diff --git a/.github/component_owners.yml b/.github/component_owners.yml index 81a0f9da79..3bbdf37c70 100644 --- a/.github/component_owners.yml +++ b/.github/component_owners.yml @@ -1,5 +1,8 @@ components: + instrumentation/opentelemetry-instrumentation-aio-pika: + - ofek1weiss + instrumentation/opentelemetry-instrumentation-boto3sqs: - oxeye-nikolay - nikosokolik @@ -11,6 +14,9 @@ components: - oxeye-nikolay - nikosokolik + instrumentation/opentelemetry-instrumentation-redis: + - sungwonh + instrumentation/opentelemetry-instrumentation-remoulade: - ben-natan - machine424 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 562b47bc52..69f9d25ee6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,7 +6,7 @@ on: - 'release/*' pull_request: env: - CORE_REPO_SHA: cad776a2031c84fb3c3a1af90ee2a939f3394b9a + CORE_REPO_SHA: c09f2076a1878c6d58b78d3895485ef5559f30f7 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: v7-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: v7-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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 0aefdf8fde..793568e433 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,20 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased](https://github.com/open-telemetry/opentelemetry-python/compare/v1.12.0rc1-0.31b0...HEAD) +## [Unreleased](https://github.com/open-telemetry/opentelemetry-python/compare/v1.12.0rc2-0.32b0...HEAD) +- Adding multiple db connections support for django-instrumentation's sqlcommenter + ([#1187](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1187)) +- SQLCommenter semicolon bug fix + ([#1200](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1200/files)) + +### Added +- `opentelemetry-instrumentation-redis` add support to instrument RedisCluster clients + ([#1177](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1177)) +- `opentelemetry-instrumentation-sqlalchemy` Added span for the connection phase ([#1133](https://github.com/open-telemetry/opentelemetry-python-contrib/issues/1133)) + +## [1.12.0rc2-0.32b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.12.0rc2-0.32b0) - 2022-07-01 + + - Pyramid: Only categorize 500s server exceptions as errors ([#1037](https://github.com/open-telemetry/opentelemetry-python-contrib/issues/1037)) @@ -17,12 +30,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `opentelemetry-instrumentation-grpc` narrow protobuf dependency to exclude protobuf >= 4 ([#1109](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1109)) - cleanup type hints for textmap `Getter` and `Setter` classes - ([#1106](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1106)) +- Suppressing downstream HTTP instrumentation to avoid [extra spans](https://github.com/open-telemetry/opentelemetry-python-contrib/issues/930) + ([#1116](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1116)) - fixed typo in `system.network.io` metric configuration ([#1135](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1135)) ### Added +- `opentelemetry-instrumentation-aiohttp-client` Add support for optional custom trace_configs argument. + ([1079](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1079)) - `opentelemetry-instrumentation-sqlalchemy` add support to instrument multiple engines ([#1132](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1132)) - `opentelemetry-instrumentation-logging` add log hook support @@ -33,6 +49,14 @@ 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)) +- `opentelemetry-instrumentation-aio-pika` added RabbitMQ aio-pika module instrumentation. + ([#1095](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1095)) +- `opentelemetry-instrumentation-requests` Restoring metrics in requests + ([#1110](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1110)) +- Integrated sqlcommenter plugin into opentelemetry-instrumentation-django + ([#896](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/896)) ## [1.12.0rc1-0.31b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.12.0rc1-0.31b0) - 2022-05-17 diff --git a/README.md b/README.md index 49d99f5ab5..7b0fb1883d 100644 --- a/README.md +++ b/README.md @@ -95,12 +95,16 @@ Meeting notes are available as a public [Google doc](https://docs.google.com/doc Approvers ([@open-telemetry/python-approvers](https://github.com/orgs/open-telemetry/teams/python-approvers)): - [Aaron Abbott](https://github.com/aabmass), Google -- [Alex Boten](https://github.com/codeboten), Lightstep - [Nathaniel Ruiz Nowell](https://github.com/NathanielRN), AWS - [Owais Lone](https://github.com/owais), Splunk - [Sanket Mehta](https://github.com/sanketmehta28), Cisco - [Ashutosh Goel](https://github.com/ashu658), Cisco +Emeritus Approvers: + +- [Hector Hernandez](https://github.com/hectorhdzg), Microsoft +- [Yusuke Tsutsumi](https://github.com/toumorokoshi), Google + *Find more about the approver role in [community repository](https://github.com/open-telemetry/community/blob/main/community-membership.md#approver).* Maintainers ([@open-telemetry/python-maintainers](https://github.com/orgs/open-telemetry/teams/python-maintainers)): @@ -109,6 +113,10 @@ Maintainers ([@open-telemetry/python-maintainers](https://github.com/orgs/open-t - [Leighton Chen](https://github.com/lzchen), Microsoft - [Srikanth Chekuri](https://github.com/srikanthccv) +Emeritus Maintainers: + +- [Alex Boten](https://github.com/codeboten), Lightstep + *Find more about the maintainer role in [community repository](https://github.com/open-telemetry/community/blob/main/community-membership.md#maintainer).* ## Running Tests Locally diff --git a/_template/version.py b/_template/version.py index d8dc1e1ed7..268a795344 100644 --- a/_template/version.py +++ b/_template/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/eachdist.ini b/eachdist.ini index 11318ef1d8..e092e11eb7 100644 --- a/eachdist.ini +++ b/eachdist.ini @@ -16,7 +16,7 @@ sortfirst= ext/* [stable] -version=1.12.0rc1 +version=1.12.0rc2 packages= opentelemetry-sdk @@ -34,7 +34,7 @@ packages= opentelemetry-api [prerelease] -version=0.31b0 +version=0.32b0 packages= all diff --git a/exporter/opentelemetry-exporter-richconsole/setup.cfg b/exporter/opentelemetry-exporter-richconsole/setup.cfg index fba1037c54..f5c8b5aba4 100644 --- a/exporter/opentelemetry-exporter-richconsole/setup.cfg +++ b/exporter/opentelemetry-exporter-richconsole/setup.cfg @@ -43,7 +43,7 @@ install_requires = rich>=10.0.0 opentelemetry-api ~= 1.3 opentelemetry-sdk ~= 1.3 - opentelemetry-semantic-conventions == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 [options.packages.find] where = src diff --git a/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/version.py b/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/version.py index d8dc1e1ed7..268a795344 100644 --- a/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/version.py +++ b/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/README.md b/instrumentation/README.md index ffb2c8bad7..71d79ea258 100644 --- a/instrumentation/README.md +++ b/instrumentation/README.md @@ -1,43 +1,44 @@ -| 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 | \ No newline at end of file +| Instrumentation | Supported Packages | Metrics support | +| --------------- | ------------------ | --------------- | +| [opentelemetry-instrumentation-aio-pika](./opentelemetry-instrumentation-aio-pika) | aio_pika ~= 7.2.0 | No +| [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 | Yes +| [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 \ No newline at end of file diff --git a/instrumentation/opentelemetry-instrumentation-aio-pika/README.rst b/instrumentation/opentelemetry-instrumentation-aio-pika/README.rst new file mode 100644 index 0000000000..aa0f1a3f5c --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-aio-pika/README.rst @@ -0,0 +1,23 @@ +OpenTelemetry Aio-pika Instrumentation +====================================== + +|pypi| + +.. |pypi| image:: https://badge.fury.io/py/opentelemetry-instrumentation-aio-pika.svg + :target: https://pypi.org/project/opentelemetry-instrumentation-aio-pika/ + +This library allows tracing requests made by the Aio-pika library. + +Installation +------------ + +:: + + pip install opentelemetry-instrumentation-aio-pika + +References +---------- + +* `OpenTelemetry Aio-pika instrumentation `_ +* `OpenTelemetry Project `_ +* `OpenTelemetry Python Examples `_ diff --git a/instrumentation/opentelemetry-instrumentation-aio-pika/setup.cfg b/instrumentation/opentelemetry-instrumentation-aio-pika/setup.cfg new file mode 100644 index 0000000000..65b327b925 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-aio-pika/setup.cfg @@ -0,0 +1,58 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +# +[metadata] +name = opentelemetry-instrumentation-aio-pika +description = OpenTelemetry Aio-pika instrumentation +long_description = file: README.rst +long_description_content_type = text/x-rst +author = OpenTelemetry Authors +author_email = cncf-opentelemetry-contributors@lists.cncf.io +url = https://github.com/open-telemetry/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-aio-pika +platforms = any +license = Apache-2.0 +classifiers = + Development Status :: 4 - Beta + Intended Audience :: Developers + License :: OSI Approved :: Apache Software License + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + +[options] +python_requires = >=3.6 +package_dir= + =src +packages=find_namespace: + +install_requires = + opentelemetry-api ~= 1.5 + wrapt >= 1.0.0, < 2.0.0 + +[options.extras_require] +test = + pytest + wrapt >= 1.0.0, < 2.0.0 + opentelemetry-test-utils == 0.32b0 + +[options.packages.find] +where = src + +[options.entry_points] +opentelemetry_instrumentor = + aio-pika = opentelemetry.instrumentation.aio_pika:AioPikaInstrumentor diff --git a/instrumentation/opentelemetry-instrumentation-aio-pika/setup.py b/instrumentation/opentelemetry-instrumentation-aio-pika/setup.py new file mode 100644 index 0000000000..bd74b8f7bc --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-aio-pika/setup.py @@ -0,0 +1,99 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + + +# DO NOT EDIT. THIS FILE WAS AUTOGENERATED FROM templates/instrumentation_setup.py.txt. +# RUN `python scripts/generate_setup.py` TO REGENERATE. + + +import distutils.cmd +import json +import os +from configparser import ConfigParser + +import setuptools + +config = ConfigParser() +config.read("setup.cfg") + +# We provide extras_require parameter to setuptools.setup later which +# overwrites the extras_require section from setup.cfg. To support extras_require +# section in setup.cfg, we load it here and merge it with the extras_require param. +extras_require = {} +if "options.extras_require" in config: + for key, value in config["options.extras_require"].items(): + extras_require[key] = [v for v in value.split("\n") if v.strip()] + +BASE_DIR = os.path.dirname(__file__) +PACKAGE_INFO = {} + +VERSION_FILENAME = os.path.join( + BASE_DIR, + "src", + "opentelemetry", + "instrumentation", + "aio_pika", + "version.py", +) +with open(VERSION_FILENAME, encoding="utf-8") as f: + exec(f.read(), PACKAGE_INFO) + +PACKAGE_FILENAME = os.path.join( + BASE_DIR, + "src", + "opentelemetry", + "instrumentation", + "aio_pika", + "package.py", +) +with open(PACKAGE_FILENAME, encoding="utf-8") as f: + exec(f.read(), PACKAGE_INFO) + +# Mark any instruments/runtime dependencies as test dependencies as well. +extras_require["instruments"] = PACKAGE_INFO["_instruments"] +test_deps = extras_require.get("test", []) +for dep in extras_require["instruments"]: + test_deps.append(dep) + +extras_require["test"] = test_deps + + +class JSONMetadataCommand(distutils.cmd.Command): + + description = ( + "print out package metadata as JSON. This is used by OpenTelemetry dev scripts to ", + "auto-generate code in other places", + ) + user_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + metadata = { + "name": config["metadata"]["name"], + "version": PACKAGE_INFO["__version__"], + "instruments": PACKAGE_INFO["_instruments"], + } + print(json.dumps(metadata)) + + +setuptools.setup( + cmdclass={"meta": JSONMetadataCommand}, + version=PACKAGE_INFO["__version__"], + extras_require=extras_require, +) diff --git a/instrumentation/opentelemetry-instrumentation-aio-pika/src/opentelemetry/instrumentation/aio_pika/__init__.py b/instrumentation/opentelemetry-instrumentation-aio-pika/src/opentelemetry/instrumentation/aio_pika/__init__.py new file mode 100644 index 0000000000..6e78db08c9 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-aio-pika/src/opentelemetry/instrumentation/aio_pika/__init__.py @@ -0,0 +1,60 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +""" +Instrument `aio_pika` to trace RabbitMQ applications. + +Usage +----- + +* Start broker backend + +.. code-block:: python + + docker run -p 5672:5672 rabbitmq + +* Run instrumented task + +.. code-block:: python + + import asyncio + + from aio_pika import Message, connect + from opentelemetry.instrumentation.aio_pika import AioPikaInstrumentor + + AioPikaInstrumentor().instrument() + + + async def main() -> None: + connection = await connect("amqp://guest:guest@localhost/") + async with connection: + channel = await connection.channel() + queue = await channel.declare_queue("hello") + await channel.default_exchange.publish( + Message(b"Hello World!"), + routing_key=queue.name, + ) + + + if __name__ == "__main__": + asyncio.run(main()) + +API +--- +""" +# pylint: disable=import-error + +from .aio_pika_instrumentor import AioPikaInstrumentor +from .version import __version__ + +__all__ = ["AioPikaInstrumentor", "__version__"] diff --git a/instrumentation/opentelemetry-instrumentation-aio-pika/src/opentelemetry/instrumentation/aio_pika/aio_pika_instrumentor.py b/instrumentation/opentelemetry-instrumentation-aio-pika/src/opentelemetry/instrumentation/aio_pika/aio_pika_instrumentor.py new file mode 100644 index 0000000000..99420d0892 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-aio-pika/src/opentelemetry/instrumentation/aio_pika/aio_pika_instrumentor.py @@ -0,0 +1,85 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +from typing import Any, Callable, Collection + +import wrapt +from aio_pika import Exchange, Queue +from aio_pika.abc import AbstractIncomingMessage + +from opentelemetry import trace +from opentelemetry.instrumentation.aio_pika.callback_decorator import ( + CallbackDecorator, +) +from opentelemetry.instrumentation.aio_pika.package import _instruments +from opentelemetry.instrumentation.aio_pika.publish_decorator import ( + PublishDecorator, +) +from opentelemetry.instrumentation.aio_pika.version import __version__ +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor +from opentelemetry.instrumentation.utils import unwrap +from opentelemetry.trace import Tracer + +_INSTRUMENTATION_MODULE_NAME = "opentelemetry.instrumentation.aio_pika" + + +class AioPikaInstrumentor(BaseInstrumentor): + @staticmethod + def _instrument_queue(tracer: Tracer): + async def wrapper(wrapped, instance, args, kwargs): + async def consume( + callback: Callable[[AbstractIncomingMessage], Any], + *fargs, + **fkwargs + ): + decorated_callback = CallbackDecorator( + tracer, instance + ).decorate(callback) + return await wrapped(decorated_callback, *fargs, **fkwargs) + + return await consume(*args, **kwargs) + + wrapt.wrap_function_wrapper(Queue, "consume", wrapper) + + @staticmethod + def _instrument_exchange(tracer: Tracer): + async def wrapper(wrapped, instance, args, kwargs): + decorated_publish = PublishDecorator(tracer, instance).decorate( + wrapped + ) + return await decorated_publish(*args, **kwargs) + + wrapt.wrap_function_wrapper(Exchange, "publish", wrapper) + + def _instrument(self, **kwargs): + tracer_provider = kwargs.get("tracer_provider", None) + tracer = trace.get_tracer( + _INSTRUMENTATION_MODULE_NAME, __version__, tracer_provider + ) + self._instrument_queue(tracer) + self._instrument_exchange(tracer) + + @staticmethod + def _uninstrument_queue(): + unwrap(Queue, "consume") + + @staticmethod + def _uninstrument_exchange(): + unwrap(Exchange, "publish") + + def _uninstrument(self, **kwargs): + self._uninstrument_queue() + self._uninstrument_exchange() + + def instrumentation_dependencies(self) -> Collection[str]: + return _instruments diff --git a/instrumentation/opentelemetry-instrumentation-aio-pika/src/opentelemetry/instrumentation/aio_pika/callback_decorator.py b/instrumentation/opentelemetry-instrumentation-aio-pika/src/opentelemetry/instrumentation/aio_pika/callback_decorator.py new file mode 100644 index 0000000000..a2169b6d18 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-aio-pika/src/opentelemetry/instrumentation/aio_pika/callback_decorator.py @@ -0,0 +1,61 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +from typing import Any, Callable, Optional + +from aio_pika import Queue +from aio_pika.abc import AbstractIncomingMessage + +from opentelemetry import context, propagate, trace +from opentelemetry.instrumentation.aio_pika.span_builder import SpanBuilder +from opentelemetry.instrumentation.aio_pika.utils import ( + is_instrumentation_enabled, +) +from opentelemetry.semconv.trace import MessagingOperationValues +from opentelemetry.trace import Span, Tracer + + +class CallbackDecorator: + def __init__(self, tracer: Tracer, queue: Queue): + self._tracer = tracer + self._queue = queue + + def _get_span(self, message: AbstractIncomingMessage) -> Optional[Span]: + builder = SpanBuilder(self._tracer) + builder.set_as_consumer() + builder.set_operation(MessagingOperationValues.RECEIVE) + builder.set_destination(message.exchange or message.routing_key) + builder.set_channel(self._queue.channel) + builder.set_message(message) + return builder.build() + + def decorate( + self, callback: Callable[[AbstractIncomingMessage], Any] + ) -> Callable[[AbstractIncomingMessage], Any]: + async def decorated(message: AbstractIncomingMessage): + if not is_instrumentation_enabled(): + return await callback(message) + headers = message.headers or {} + ctx = propagate.extract(headers) + token = context.attach(ctx) + span = self._get_span(message) + if not span: + return await callback(message) + try: + with trace.use_span(span, end_on_exit=True): + return_value = await callback(message) + finally: + context.detach(token) + return return_value + + return decorated diff --git a/instrumentation/opentelemetry-instrumentation-aio-pika/src/opentelemetry/instrumentation/aio_pika/package.py b/instrumentation/opentelemetry-instrumentation-aio-pika/src/opentelemetry/instrumentation/aio_pika/package.py new file mode 100644 index 0000000000..6c7ed74ea4 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-aio-pika/src/opentelemetry/instrumentation/aio_pika/package.py @@ -0,0 +1,16 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +from typing import Collection + +_instruments: Collection[str] = ("aio_pika ~= 7.2.0",) diff --git a/instrumentation/opentelemetry-instrumentation-aio-pika/src/opentelemetry/instrumentation/aio_pika/publish_decorator.py b/instrumentation/opentelemetry-instrumentation-aio-pika/src/opentelemetry/instrumentation/aio_pika/publish_decorator.py new file mode 100644 index 0000000000..cae834a031 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-aio-pika/src/opentelemetry/instrumentation/aio_pika/publish_decorator.py @@ -0,0 +1,53 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +from typing import Callable, Optional + +import aiormq +from aio_pika import Exchange +from aio_pika.abc import AbstractMessage + +from opentelemetry import propagate, trace +from opentelemetry.instrumentation.aio_pika.span_builder import SpanBuilder +from opentelemetry.trace import Span, Tracer + + +class PublishDecorator: + def __init__(self, tracer: Tracer, exchange: Exchange): + self._tracer = tracer + self._exchange = exchange + + def _get_publish_span( + self, message: AbstractMessage, routing_key: str + ) -> Optional[Span]: + builder = SpanBuilder(self._tracer) + builder.set_as_producer() + builder.set_destination(f"{self._exchange.name},{routing_key}") + builder.set_channel(self._exchange.channel) + builder.set_message(message) + return builder.build() + + def decorate(self, publish: Callable) -> Callable: + async def decorated_publish( + message: AbstractMessage, routing_key: str, **kwargs + ) -> Optional[aiormq.abc.ConfirmationFrameType]: + span = self._get_publish_span(message, routing_key) + if not span: + return await publish(message, routing_key, **kwargs) + with trace.use_span(span, end_on_exit=True): + if span.is_recording(): + propagate.inject(message.properties.headers) + return_value = await publish(message, routing_key, **kwargs) + return return_value + + return decorated_publish diff --git a/instrumentation/opentelemetry-instrumentation-aio-pika/src/opentelemetry/instrumentation/aio_pika/span_builder.py b/instrumentation/opentelemetry-instrumentation-aio-pika/src/opentelemetry/instrumentation/aio_pika/span_builder.py new file mode 100644 index 0000000000..a61209e0ce --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-aio-pika/src/opentelemetry/instrumentation/aio_pika/span_builder.py @@ -0,0 +1,77 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +from typing import Optional + +from aio_pika.abc import AbstractChannel, AbstractMessage + +from opentelemetry.instrumentation.aio_pika.utils import ( + is_instrumentation_enabled, +) +from opentelemetry.semconv.trace import ( + MessagingOperationValues, + SpanAttributes, +) +from opentelemetry.trace import Span, SpanKind, Tracer + +_DEFAULT_ATTRIBUTES = {SpanAttributes.MESSAGING_SYSTEM: 'rabbitmq'} + + +class SpanBuilder: + def __init__(self, tracer: Tracer): + self._tracer = tracer + self._attributes = _DEFAULT_ATTRIBUTES.copy() + self._operation: MessagingOperationValues = None + self._kind: SpanKind = None + self._destination: str = None + + def set_as_producer(self): + self._kind = SpanKind.PRODUCER + + def set_as_consumer(self): + self._kind = SpanKind.CONSUMER + + def set_operation(self, operation: MessagingOperationValues): + self._operation = operation + + def set_destination(self, destination: str): + self._destination = destination + self._attributes[SpanAttributes.MESSAGING_DESTINATION] = destination + + def set_channel(self, channel: AbstractChannel): + url = channel.connection.connection.url + self._attributes.update({ + SpanAttributes.NET_PEER_NAME: url.host, + SpanAttributes.NET_PEER_PORT: url.port + }) + + def set_message(self, message: AbstractMessage): + properties = message.properties + if properties.message_id: + self._attributes[SpanAttributes.MESSAGING_MESSAGE_ID] = properties.message_id + if properties.correlation_id: + self._attributes[SpanAttributes.MESSAGING_CONVERSATION_ID] = properties.correlation_id + + def build(self) -> Optional[Span]: + if not is_instrumentation_enabled(): + return None + if self._operation: + self._attributes[SpanAttributes.MESSAGING_OPERATION] = self._operation.value + else: + self._attributes[SpanAttributes.MESSAGING_TEMP_DESTINATION] = True + span = self._tracer.start_span(self._generate_span_name(), kind=self._kind, attributes=self._attributes) + return span + + def _generate_span_name(self) -> str: + operation_value = self._operation.value if self._operation else 'send' + return f'{self._destination} {operation_value}' diff --git a/instrumentation/opentelemetry-instrumentation-aio-pika/src/opentelemetry/instrumentation/aio_pika/utils.py b/instrumentation/opentelemetry-instrumentation-aio-pika/src/opentelemetry/instrumentation/aio_pika/utils.py new file mode 100644 index 0000000000..fb94ddf468 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-aio-pika/src/opentelemetry/instrumentation/aio_pika/utils.py @@ -0,0 +1,9 @@ +from opentelemetry import context + + +def is_instrumentation_enabled() -> bool: + if context.get_value("suppress_instrumentation") or context.get_value( + context._SUPPRESS_INSTRUMENTATION_KEY + ): + return False + return True diff --git a/instrumentation/opentelemetry-instrumentation-aio-pika/src/opentelemetry/instrumentation/aio_pika/version.py b/instrumentation/opentelemetry-instrumentation-aio-pika/src/opentelemetry/instrumentation/aio_pika/version.py new file mode 100644 index 0000000000..268a795344 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-aio-pika/src/opentelemetry/instrumentation/aio_pika/version.py @@ -0,0 +1,15 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-aio-pika/tests/__init__.py b/instrumentation/opentelemetry-instrumentation-aio-pika/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/instrumentation/opentelemetry-instrumentation-aio-pika/tests/consts.py b/instrumentation/opentelemetry-instrumentation-aio-pika/tests/consts.py new file mode 100644 index 0000000000..ada7080192 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-aio-pika/tests/consts.py @@ -0,0 +1,26 @@ +from argparse import Namespace + +from yarl import URL + +MESSAGE_ID = "meesage_id" +CORRELATION_ID = "correlation_id" +MESSAGING_SYSTEM = "rabbitmq" +EXCHANGE_NAME = "exchange_name" +QUEUE_NAME = "queue_name" +ROUTING_KEY = "routing_key" +SERVER_HOST = "localhost" +SERVER_PORT = 1234 +SERVER_USER = "guest" +SERVER_PASS = "guest" +SERVER_URL = URL( + f"amqp://{SERVER_USER}:{SERVER_PASS}@{SERVER_HOST}:{SERVER_PORT}/" +) +CONNECTION = Namespace(connection=Namespace(url=SERVER_URL)) +CHANNEL = Namespace(connection=CONNECTION, loop=None) +MESSAGE = Namespace( + properties=Namespace( + message_id=MESSAGE_ID, correlation_id=CORRELATION_ID, headers={} + ), + exchange=EXCHANGE_NAME, + headers={}, +) diff --git a/instrumentation/opentelemetry-instrumentation-aio-pika/tests/test_aio_pika_instrumentation.py b/instrumentation/opentelemetry-instrumentation-aio-pika/tests/test_aio_pika_instrumentation.py new file mode 100644 index 0000000000..c2dc6e5144 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-aio-pika/tests/test_aio_pika_instrumentation.py @@ -0,0 +1,34 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +from unittest import TestCase + +import wrapt +from aio_pika import Exchange, Queue + +from opentelemetry.instrumentation.aio_pika import AioPikaInstrumentor + + +class TestPika(TestCase): + def test_instrument_api(self) -> None: + instrumentation = AioPikaInstrumentor() + instrumentation.instrument() + self.assertTrue(isinstance(Queue.consume, wrapt.BoundFunctionWrapper)) + self.assertTrue( + isinstance(Exchange.publish, wrapt.BoundFunctionWrapper) + ) + instrumentation.uninstrument() + self.assertFalse(isinstance(Queue.consume, wrapt.BoundFunctionWrapper)) + self.assertFalse( + isinstance(Exchange.publish, wrapt.BoundFunctionWrapper) + ) diff --git a/instrumentation/opentelemetry-instrumentation-aio-pika/tests/test_callback_decorator.py b/instrumentation/opentelemetry-instrumentation-aio-pika/tests/test_callback_decorator.py new file mode 100644 index 0000000000..70883c116c --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-aio-pika/tests/test_callback_decorator.py @@ -0,0 +1,74 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +import asyncio +from unittest import TestCase, mock + +from aio_pika import Queue + +from opentelemetry.instrumentation.aio_pika.callback_decorator import ( + CallbackDecorator, +) +from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.trace import SpanKind, get_tracer + +from .consts import ( + CHANNEL, + CORRELATION_ID, + EXCHANGE_NAME, + MESSAGE, + MESSAGE_ID, + MESSAGING_SYSTEM, + QUEUE_NAME, + SERVER_HOST, + SERVER_PORT, +) + + +class TestInstrumentedQueue(TestCase): + EXPECTED_ATTRIBUTES = { + SpanAttributes.MESSAGING_SYSTEM: MESSAGING_SYSTEM, + SpanAttributes.MESSAGING_DESTINATION: EXCHANGE_NAME, + SpanAttributes.NET_PEER_NAME: SERVER_HOST, + SpanAttributes.NET_PEER_PORT: SERVER_PORT, + SpanAttributes.MESSAGING_MESSAGE_ID: MESSAGE_ID, + SpanAttributes.MESSAGING_CONVERSATION_ID: CORRELATION_ID, + SpanAttributes.MESSAGING_OPERATION: "receive", + } + + def setUp(self): + self.tracer = get_tracer(__name__) + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) + + def test_get_callback_span(self): + queue = Queue(CHANNEL, QUEUE_NAME, False, False, False, None) + tracer = mock.MagicMock() + CallbackDecorator(tracer, queue)._get_span(MESSAGE) + tracer.start_span.assert_called_once_with( + f"{EXCHANGE_NAME} receive", + kind=SpanKind.CONSUMER, + attributes=self.EXPECTED_ATTRIBUTES, + ) + + def test_decorate_callback(self): + queue = Queue(CHANNEL, QUEUE_NAME, False, False, False, None) + callback = mock.MagicMock(return_value=asyncio.sleep(0)) + with mock.patch.object( + CallbackDecorator, "_get_span" + ) as mocked_get_callback_span: + callback_decorator = CallbackDecorator(self.tracer, queue) + decorated_callback = callback_decorator.decorate(callback) + self.loop.run_until_complete(decorated_callback(MESSAGE)) + mocked_get_callback_span.assert_called_once() + callback.assert_called_once_with(MESSAGE) diff --git a/instrumentation/opentelemetry-instrumentation-aio-pika/tests/test_publish_decorator.py b/instrumentation/opentelemetry-instrumentation-aio-pika/tests/test_publish_decorator.py new file mode 100644 index 0000000000..80dfa3182b --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-aio-pika/tests/test_publish_decorator.py @@ -0,0 +1,89 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +import asyncio +from typing import Type +from unittest import TestCase, mock + +from aio_pika import Exchange, RobustExchange + +from opentelemetry.instrumentation.aio_pika.publish_decorator import ( + PublishDecorator, +) +from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.trace import SpanKind, get_tracer + +from .consts import ( + CHANNEL, + CONNECTION, + CORRELATION_ID, + EXCHANGE_NAME, + MESSAGE, + MESSAGE_ID, + MESSAGING_SYSTEM, + ROUTING_KEY, + SERVER_HOST, + SERVER_PORT, +) + + +class TestInstrumentedExchange(TestCase): + EXPECTED_ATTRIBUTES = { + SpanAttributes.MESSAGING_SYSTEM: MESSAGING_SYSTEM, + SpanAttributes.MESSAGING_DESTINATION: f"{EXCHANGE_NAME},{ROUTING_KEY}", + SpanAttributes.NET_PEER_NAME: SERVER_HOST, + SpanAttributes.NET_PEER_PORT: SERVER_PORT, + SpanAttributes.MESSAGING_MESSAGE_ID: MESSAGE_ID, + SpanAttributes.MESSAGING_CONVERSATION_ID: CORRELATION_ID, + SpanAttributes.MESSAGING_TEMP_DESTINATION: True, + } + + def setUp(self): + self.tracer = get_tracer(__name__) + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) + + def test_get_publish_span(self): + exchange = Exchange(CONNECTION, CHANNEL, EXCHANGE_NAME) + tracer = mock.MagicMock() + PublishDecorator(tracer, exchange)._get_publish_span( + MESSAGE, ROUTING_KEY + ) + tracer.start_span.assert_called_once_with( + f"{EXCHANGE_NAME},{ROUTING_KEY} send", + kind=SpanKind.PRODUCER, + attributes=self.EXPECTED_ATTRIBUTES, + ) + + def _test_publish(self, exchange_type: Type[Exchange]): + exchange = exchange_type(CONNECTION, CHANNEL, EXCHANGE_NAME) + with mock.patch.object( + PublishDecorator, "_get_publish_span" + ) as mock_get_publish_span: + with mock.patch.object( + Exchange, "publish", return_value=asyncio.sleep(0) + ) as mock_publish: + decorated_publish = PublishDecorator( + self.tracer, exchange + ).decorate(mock_publish) + self.loop.run_until_complete( + decorated_publish(MESSAGE, ROUTING_KEY) + ) + mock_publish.assert_called_once() + mock_get_publish_span.assert_called_once() + + def test_publish(self): + self._test_publish(Exchange) + + def test_robust_publish(self): + self._test_publish(RobustExchange) diff --git a/instrumentation/opentelemetry-instrumentation-aio-pika/tests/test_span_builder.py b/instrumentation/opentelemetry-instrumentation-aio-pika/tests/test_span_builder.py new file mode 100644 index 0000000000..5f87d53846 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-aio-pika/tests/test_span_builder.py @@ -0,0 +1,26 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +from unittest import TestCase + +from opentelemetry.instrumentation.aio_pika.span_builder import SpanBuilder +from opentelemetry.trace import Span, get_tracer + + +class TestBuilder(TestCase): + def test_build(self): + builder = SpanBuilder(get_tracer(__name__)) + builder.set_as_consumer() + builder.set_destination('destination') + span = builder.build() + self.assertTrue(isinstance(span, Span)) diff --git a/instrumentation/opentelemetry-instrumentation-aiohttp-client/setup.cfg b/instrumentation/opentelemetry-instrumentation-aiohttp-client/setup.cfg index ef2adfe27b..0f88035410 100644 --- a/instrumentation/opentelemetry-instrumentation-aiohttp-client/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-aiohttp-client/setup.cfg @@ -41,9 +41,9 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-semantic-conventions == 0.31b0 - opentelemetry-instrumentation == 0.31b0 - opentelemetry-util-http == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 + opentelemetry-instrumentation == 0.32b0 + opentelemetry-util-http == 0.32b0 wrapt >= 1.0.0, < 2.0.0 [options.packages.find] diff --git a/instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/__init__.py b/instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/__init__.py index 49652a2c11..e2eaaa7442 100644 --- a/instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/__init__.py @@ -262,18 +262,24 @@ def _instrument( url_filter: _UrlFilterT = None, request_hook: _RequestHookT = None, response_hook: _ResponseHookT = None, + trace_configs: typing.Optional[aiohttp.TraceConfig] = None, ): """Enables tracing of all ClientSessions When a ClientSession gets created a TraceConfig is automatically added to the session's trace_configs. """ + + if trace_configs is None: + trace_configs = [] + # pylint:disable=unused-argument def instrumented_init(wrapped, instance, args, kwargs): if context_api.get_value(_SUPPRESS_INSTRUMENTATION_KEY): return wrapped(*args, **kwargs) - trace_configs = list(kwargs.get("trace_configs") or ()) + if kwargs.get("trace_configs"): + trace_configs.extend(kwargs.get("trace_configs")) trace_config = create_trace_config( url_filter=url_filter, @@ -328,12 +334,15 @@ def _instrument(self, **kwargs): such as API keys or user personal information. ``request_hook``: An optional callback that is invoked right after a span is created. ``response_hook``: An optional callback which is invoked right before the span is finished processing a response. + ``trace_configs``: An optional list of aiohttp.TraceConfig items, allowing customize enrichment of spans + based on aiohttp events (see specification: https://docs.aiohttp.org/en/stable/tracing_reference.html) """ _instrument( tracer_provider=kwargs.get("tracer_provider"), url_filter=kwargs.get("url_filter"), request_hook=kwargs.get("request_hook"), response_hook=kwargs.get("response_hook"), + trace_configs=kwargs.get("trace_configs"), ) def _uninstrument(self, **kwargs): diff --git a/instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/version.py b/instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/version.py index abd20005df..0eb5bcc268 100644 --- a/instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/version.py +++ b/instrumentation/opentelemetry-instrumentation-aiohttp-client/src/opentelemetry/instrumentation/aiohttp_client/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-aiohttp-client/tests/test_aiohttp_client_integration.py b/instrumentation/opentelemetry-instrumentation-aiohttp-client/tests/test_aiohttp_client_integration.py index 800ee1ba6b..92ca8f55be 100644 --- a/instrumentation/opentelemetry-instrumentation-aiohttp-client/tests/test_aiohttp_client_integration.py +++ b/instrumentation/opentelemetry-instrumentation-aiohttp-client/tests/test_aiohttp_client_integration.py @@ -386,6 +386,20 @@ def test_instrument(self): ) self.assertEqual(200, span.attributes[SpanAttributes.HTTP_STATUS_CODE]) + def test_instrument_with_custom_trace_config(self): + AioHttpClientInstrumentor().uninstrument() + AioHttpClientInstrumentor().instrument( + trace_configs=[aiohttp_client.create_trace_config()] + ) + + self.assert_spans(0) + + run_with_test_server( + self.get_default_request(), self.URL, self.default_handler + ) + + self.assert_spans(2) + def test_instrument_with_existing_trace_config(self): trace_config = aiohttp.TraceConfig() @@ -432,7 +446,7 @@ async def uninstrument_request(server: aiohttp.test_utils.TestServer): run_with_test_server( self.get_default_request(), self.URL, self.default_handler ) - self.assert_spans(1) + self.assert_spans(2) def test_suppress_instrumentation(self): token = context.attach( diff --git a/instrumentation/opentelemetry-instrumentation-aiopg/setup.cfg b/instrumentation/opentelemetry-instrumentation-aiopg/setup.cfg index ba22420b5c..318059e460 100644 --- a/instrumentation/opentelemetry-instrumentation-aiopg/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-aiopg/setup.cfg @@ -41,14 +41,14 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-instrumentation-dbapi == 0.31b0 - opentelemetry-instrumentation == 0.31b0 + opentelemetry-instrumentation-dbapi == 0.32b0 + opentelemetry-instrumentation == 0.32b0 wrapt >= 1.0.0, < 2.0.0 [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 - opentelemetry-semantic-conventions == 0.31b0 + opentelemetry-test-utils == 0.32b0 + opentelemetry-semantic-conventions == 0.32b0 [options.packages.find] where = src diff --git a/instrumentation/opentelemetry-instrumentation-aiopg/src/opentelemetry/instrumentation/aiopg/version.py b/instrumentation/opentelemetry-instrumentation-aiopg/src/opentelemetry/instrumentation/aiopg/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-aiopg/src/opentelemetry/instrumentation/aiopg/version.py +++ b/instrumentation/opentelemetry-instrumentation-aiopg/src/opentelemetry/instrumentation/aiopg/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-asgi/setup.cfg b/instrumentation/opentelemetry-instrumentation-asgi/setup.cfg index 3fbe33e940..55a8b56269 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-asgi/setup.cfg @@ -41,14 +41,14 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-semantic-conventions == 0.31b0 - opentelemetry-instrumentation == 0.31b0 - opentelemetry-util-http == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 + opentelemetry-instrumentation == 0.32b0 + opentelemetry-util-http == 0.32b0 asgiref ~= 3.0 [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 [options.packages.find] where = src diff --git a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/version.py b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/version.py +++ b/instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-asyncpg/setup.cfg b/instrumentation/opentelemetry-instrumentation-asyncpg/setup.cfg index 0839823ea2..3bd41ec18c 100644 --- a/instrumentation/opentelemetry-instrumentation-asyncpg/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-asyncpg/setup.cfg @@ -41,12 +41,12 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-semantic-conventions == 0.31b0 - opentelemetry-instrumentation == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 + opentelemetry-instrumentation == 0.32b0 [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 [options.packages.find] where = src diff --git a/instrumentation/opentelemetry-instrumentation-asyncpg/src/opentelemetry/instrumentation/asyncpg/version.py b/instrumentation/opentelemetry-instrumentation-asyncpg/src/opentelemetry/instrumentation/asyncpg/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-asyncpg/src/opentelemetry/instrumentation/asyncpg/version.py +++ b/instrumentation/opentelemetry-instrumentation-asyncpg/src/opentelemetry/instrumentation/asyncpg/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-aws-lambda/setup.cfg b/instrumentation/opentelemetry-instrumentation-aws-lambda/setup.cfg index 4672184d24..fee5018e04 100644 --- a/instrumentation/opentelemetry-instrumentation-aws-lambda/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-aws-lambda/setup.cfg @@ -38,13 +38,13 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-instrumentation == 0.31b0 + opentelemetry-instrumentation == 0.32b0 opentelemetry-propagator-aws-xray == 1.0.1 - opentelemetry-semantic-conventions == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 [options.packages.find] where = src diff --git a/instrumentation/opentelemetry-instrumentation-aws-lambda/src/opentelemetry/instrumentation/aws_lambda/version.py b/instrumentation/opentelemetry-instrumentation-aws-lambda/src/opentelemetry/instrumentation/aws_lambda/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-aws-lambda/src/opentelemetry/instrumentation/aws_lambda/version.py +++ b/instrumentation/opentelemetry-instrumentation-aws-lambda/src/opentelemetry/instrumentation/aws_lambda/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-boto/setup.cfg b/instrumentation/opentelemetry-instrumentation-boto/setup.cfg index 7aeaaa3b91..f5fa7ff568 100644 --- a/instrumentation/opentelemetry-instrumentation-boto/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-boto/setup.cfg @@ -41,13 +41,13 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-semantic-conventions == 0.31b0 - opentelemetry-instrumentation == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 + opentelemetry-instrumentation == 0.32b0 [options.extras_require] test = moto~=2.0 - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 markupsafe==2.0.1 [options.packages.find] diff --git a/instrumentation/opentelemetry-instrumentation-boto/src/opentelemetry/instrumentation/boto/version.py b/instrumentation/opentelemetry-instrumentation-boto/src/opentelemetry/instrumentation/boto/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-boto/src/opentelemetry/instrumentation/boto/version.py +++ b/instrumentation/opentelemetry-instrumentation-boto/src/opentelemetry/instrumentation/boto/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-boto3sqs/setup.cfg b/instrumentation/opentelemetry-instrumentation-boto3sqs/setup.cfg index 971857e024..317bf2449e 100644 --- a/instrumentation/opentelemetry-instrumentation-boto3sqs/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-boto3sqs/setup.cfg @@ -56,4 +56,4 @@ where = src [options.entry_points] opentelemetry_instrumentor = - boto3sqs = opentelemetry.instrumentation.boto3sqs:Boto3SQSInstrumentation + boto3 = opentelemetry.instrumentation.boto3sqs:Boto3SQSInstrumentor diff --git a/instrumentation/opentelemetry-instrumentation-boto3sqs/src/opentelemetry/instrumentation/boto3sqs/version.py b/instrumentation/opentelemetry-instrumentation-boto3sqs/src/opentelemetry/instrumentation/boto3sqs/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-boto3sqs/src/opentelemetry/instrumentation/boto3sqs/version.py +++ b/instrumentation/opentelemetry-instrumentation-boto3sqs/src/opentelemetry/instrumentation/boto3sqs/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-botocore/setup.cfg b/instrumentation/opentelemetry-instrumentation-botocore/setup.cfg index b6727fedc8..4f39bb1a4b 100644 --- a/instrumentation/opentelemetry-instrumentation-botocore/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-botocore/setup.cfg @@ -41,13 +41,13 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-semantic-conventions == 0.31b0 - opentelemetry-instrumentation == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 + opentelemetry-instrumentation == 0.32b0 [options.extras_require] test = moto[all] ~= 2.2.6 - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 markupsafe==2.0.1 [options.packages.find] diff --git a/instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/__init__.py b/instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/__init__.py index cf64e65d83..40a760d525 100644 --- a/instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/__init__.py @@ -87,6 +87,9 @@ def response_hook(span, service_name, operation_name, result): from wrapt import wrap_function_wrapper from opentelemetry import context as context_api + +# FIXME: fix the importing of this private attribute when the location of the _SUPPRESS_HTTP_INSTRUMENTATION_KEY is defined. +from opentelemetry.context import _SUPPRESS_HTTP_INSTRUMENTATION_KEY from opentelemetry.instrumentation.botocore.extensions import _find_extension from opentelemetry.instrumentation.botocore.extensions.types import ( _AwsSdkCallContext, @@ -105,13 +108,6 @@ def response_hook(span, service_name, operation_name, result): logger = logging.getLogger(__name__) -# A key to a context variable to avoid creating duplicate spans when instrumenting -# both botocore.client and urllib3.connectionpool.HTTPConnectionPool.urlopen since -# botocore calls urlopen -_SUPPRESS_HTTP_INSTRUMENTATION_KEY = context_api.create_key( - "suppress_http_instrumentation" -) - # pylint: disable=unused-argument def _patched_endpoint_prepare_request(wrapped, instance, args, kwargs): diff --git a/instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/version.py b/instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/version.py +++ b/instrumentation/opentelemetry-instrumentation-botocore/src/opentelemetry/instrumentation/botocore/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-botocore/tests/test_botocore_instrumentation.py b/instrumentation/opentelemetry-instrumentation-botocore/tests/test_botocore_instrumentation.py index c7249c9afb..9a5d8429b5 100644 --- a/instrumentation/opentelemetry-instrumentation-botocore/tests/test_botocore_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-botocore/tests/test_botocore_instrumentation.py @@ -27,7 +27,12 @@ ) from opentelemetry import trace as trace_api -from opentelemetry.context import attach, detach, set_value +from opentelemetry.context import ( + _SUPPRESS_HTTP_INSTRUMENTATION_KEY, + attach, + detach, + set_value, +) from opentelemetry.instrumentation.botocore import BotocoreInstrumentor from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY from opentelemetry.propagate import get_global_textmap, set_global_textmap @@ -326,6 +331,17 @@ def test_suppress_instrumentation_xray_client(self): detach(token) self.assertEqual(0, len(self.get_finished_spans())) + @mock_xray + def test_suppress_http_instrumentation_xray_client(self): + xray_client = self._make_client("xray") + token = attach(set_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY, True)) + try: + xray_client.put_trace_segments(TraceSegmentDocuments=["str1"]) + xray_client.put_trace_segments(TraceSegmentDocuments=["str2"]) + finally: + detach(token) + self.assertEqual(2, len(self.get_finished_spans())) + @mock_s3 def test_request_hook(self): request_hook_service_attribute_name = "request_hook.service_name" diff --git a/instrumentation/opentelemetry-instrumentation-celery/setup.cfg b/instrumentation/opentelemetry-instrumentation-celery/setup.cfg index 7c6363e9a9..c5c31c79e0 100644 --- a/instrumentation/opentelemetry-instrumentation-celery/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-celery/setup.cfg @@ -41,13 +41,13 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-semantic-conventions == 0.31b0 - opentelemetry-instrumentation == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 + opentelemetry-instrumentation == 0.32b0 [options.extras_require] test = pytest - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 [options.packages.find] where = src diff --git a/instrumentation/opentelemetry-instrumentation-celery/src/opentelemetry/instrumentation/celery/version.py b/instrumentation/opentelemetry-instrumentation-celery/src/opentelemetry/instrumentation/celery/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-celery/src/opentelemetry/instrumentation/celery/version.py +++ b/instrumentation/opentelemetry-instrumentation-celery/src/opentelemetry/instrumentation/celery/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-confluent-kafka/src/opentelemetry/instrumentation/confluent_kafka/version.py b/instrumentation/opentelemetry-instrumentation-confluent-kafka/src/opentelemetry/instrumentation/confluent_kafka/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-confluent-kafka/src/opentelemetry/instrumentation/confluent_kafka/version.py +++ b/instrumentation/opentelemetry-instrumentation-confluent-kafka/src/opentelemetry/instrumentation/confluent_kafka/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-dbapi/setup.cfg b/instrumentation/opentelemetry-instrumentation-dbapi/setup.cfg index b37eea56d6..fd1aaf074a 100644 --- a/instrumentation/opentelemetry-instrumentation-dbapi/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-dbapi/setup.cfg @@ -41,13 +41,13 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-semantic-conventions == 0.31b0 - opentelemetry-instrumentation == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 + opentelemetry-instrumentation == 0.32b0 wrapt >= 1.0.0, < 2.0.0 [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 [options.packages.find] where = src diff --git a/instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/__init__.py b/instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/__init__.py index 2559466221..397a80b2bd 100644 --- a/instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/__init__.py @@ -46,12 +46,12 @@ from opentelemetry import trace as trace_api from opentelemetry.instrumentation.dbapi.version import __version__ from opentelemetry.instrumentation.utils import ( - _generate_opentelemetry_traceparent, - _generate_sql_comment, + _add_sql_comment, + _get_opentelemetry_values, unwrap, ) from opentelemetry.semconv.trace import SpanAttributes -from opentelemetry.trace import Span, SpanKind, TracerProvider, get_tracer +from opentelemetry.trace import SpanKind, TracerProvider, get_tracer _logger = logging.getLogger(__name__) @@ -375,15 +375,6 @@ def get_statement(self, cursor, args): # pylint: disable=no-self-use return statement.decode("utf8", "replace") return statement - @staticmethod - def _generate_comment(span: Span) -> str: - span_context = span.get_span_context() - meta = {} - if span_context.is_valid: - meta.update(_generate_opentelemetry_traceparent(span)) - # TODO(schekuri): revisit to enrich with info such as route, db_driver etc... - return _generate_sql_comment(**meta) - def traced_execution( self, cursor, @@ -405,11 +396,14 @@ def traced_execution( self._populate_span(span, cursor, *args) if args and self._commenter_enabled: try: - comment = self._generate_comment(span) - if isinstance(args[0], bytes): - comment = comment.encode("utf8") args_list = list(args) - args_list[0] += comment + commenter_data = {} + commenter_data.update(_get_opentelemetry_values()) + statement = _add_sql_comment( + args_list[0], **commenter_data + ) + + args_list[0] = statement args = tuple(args_list) except Exception as exc: # pylint: disable=broad-except _logger.exception( diff --git a/instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/version.py b/instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/version.py index ca9e66477d..d0074fee82 100644 --- a/instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/version.py +++ b/instrumentation/opentelemetry-instrumentation-dbapi/src/opentelemetry/instrumentation/dbapi/version.py @@ -12,6 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" _instruments = tuple() diff --git a/instrumentation/opentelemetry-instrumentation-dbapi/tests/test_dbapi_integration.py b/instrumentation/opentelemetry-instrumentation-dbapi/tests/test_dbapi_integration.py index 2644cb2fcd..1ec5cd9df2 100644 --- a/instrumentation/opentelemetry-instrumentation-dbapi/tests/test_dbapi_integration.py +++ b/instrumentation/opentelemetry-instrumentation-dbapi/tests/test_dbapi_integration.py @@ -236,12 +236,11 @@ def test_executemany_comment(self): mock_connect, {}, {} ) cursor = mock_connection.cursor() - cursor.executemany("Test query") - spans_list = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans_list), 1) - span = spans_list[0] - comment = dbapi.CursorTracer._generate_comment(span) - self.assertIn(comment, cursor.query) + cursor.executemany("Select 1;") + self.assertRegex( + cursor.query, + r"Select 1 /\*traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;", + ) def test_callproc(self): db_integration = dbapi.DatabaseApiIntegration( diff --git a/instrumentation/opentelemetry-instrumentation-django/setup.cfg b/instrumentation/opentelemetry-instrumentation-django/setup.cfg index 702723e7dd..5b3c5a7a93 100644 --- a/instrumentation/opentelemetry-instrumentation-django/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-django/setup.cfg @@ -40,17 +40,17 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-util-http == 0.31b0 - opentelemetry-instrumentation-wsgi == 0.31b0 - opentelemetry-instrumentation == 0.31b0 + opentelemetry-util-http == 0.32b0 + opentelemetry-instrumentation-wsgi == 0.32b0 + opentelemetry-instrumentation == 0.32b0 opentelemetry-api ~= 1.3 - opentelemetry-semantic-conventions == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 [options.extras_require] asgi = - opentelemetry-instrumentation-asgi == 0.31b0 + opentelemetry-instrumentation-asgi == 0.32b0 test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 [options.packages.find] where = src diff --git a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/__init__.py b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/__init__.py index ad6fa7bf36..4b8dec4e64 100644 --- a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/__init__.py @@ -17,6 +17,68 @@ .. _django: https://pypi.org/project/django/ +SQLCOMMENTER +***************************************** +You can optionally configure Django instrumentation to enable sqlcommenter which enriches +the query with contextual information. + +Usage +----- + +.. code:: python + + from opentelemetry.instrumentation.django import DjangoInstrumentor + + DjangoInstrumentor().instrument(is_sql_commentor_enabled=True) + + +For example, +:: + + Invoking Users().objects.all() will lead to sql query "select * from auth_users" but when SQLCommenter is enabled + the query will get appended with some configurable tags like "select * from auth_users /*metrics=value*/;" + + +SQLCommenter Configurations +*************************** +We can configure the tags to be appended to the sqlquery log by adding below variables to the settings.py + +SQLCOMMENTER_WITH_FRAMEWORK = True(Default) or False + +For example, +:: +Enabling this flag will add django framework and it's version which is /*framework='django%3A2.2.3*/ + +SQLCOMMENTER_WITH_CONTROLLER = True(Default) or False + +For example, +:: +Enabling this flag will add controller name that handles the request /*controller='index'*/ + +SQLCOMMENTER_WITH_ROUTE = True(Default) or False + +For example, +:: +Enabling this flag will add url path that handles the request /*route='polls/'*/ + +SQLCOMMENTER_WITH_APP_NAME = True(Default) or False + +For example, +:: +Enabling this flag will add app name that handles the request /*app_name='polls'*/ + +SQLCOMMENTER_WITH_OPENTELEMETRY = True(Default) or False + +For example, +:: +Enabling this flag will add opentelemetry traceparent /*traceparent='00-fd720cffceba94bbf75940ff3caaf3cc-4fd1a2bdacf56388-01'*/ + +SQLCOMMENTER_WITH_DB_DRIVER = True(Default) or False + +For example, +:: +Enabling this flag will add name of the db driver /*db_driver='django.db.backends.postgresql'*/ + Usage ----- @@ -124,6 +186,7 @@ def response_hook(span, request, response): API --- + """ from logging import getLogger @@ -136,7 +199,9 @@ def response_hook(span, request, response): from opentelemetry.instrumentation.django.environment_variables import ( OTEL_PYTHON_DJANGO_INSTRUMENT, ) -from opentelemetry.instrumentation.django.middleware import _DjangoMiddleware +from opentelemetry.instrumentation.django.middleware.otel_middleware import ( + _DjangoMiddleware, +) from opentelemetry.instrumentation.django.package import _instruments from opentelemetry.instrumentation.django.version import __version__ from opentelemetry.instrumentation.instrumentor import BaseInstrumentor @@ -166,6 +231,8 @@ class DjangoInstrumentor(BaseInstrumentor): [_DjangoMiddleware.__module__, _DjangoMiddleware.__qualname__] ) + _sql_commenter_middleware = "opentelemetry.instrumentation.django.middleware.sqlcommenter_middleware.SqlCommenter" + def instrumentation_dependencies(self) -> Collection[str]: return _instruments @@ -204,7 +271,13 @@ def _instrument(self, **kwargs): if isinstance(settings_middleware, tuple): settings_middleware = list(settings_middleware) + is_sql_commentor_enabled = kwargs.pop("is_sql_commentor_enabled", None) + + if is_sql_commentor_enabled: + settings_middleware.insert(0, self._sql_commenter_middleware) + settings_middleware.insert(0, self._opentelemetry_middleware) + setattr(settings, _middleware_setting, settings_middleware) def _uninstrument(self, **kwargs): diff --git a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/__init__.py b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware.py b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py similarity index 100% rename from instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware.py rename to instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/otel_middleware.py diff --git a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py new file mode 100644 index 0000000000..89c1b93008 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware/sqlcommenter_middleware.py @@ -0,0 +1,121 @@ +#!/usr/bin/python +# +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +from contextlib import ExitStack +from logging import getLogger +from typing import Any, Type, TypeVar + +# pylint: disable=no-name-in-module +from django import conf, get_version +from django.db import connections +from django.db.backends.utils import CursorDebugWrapper + +from opentelemetry.instrumentation.utils import ( + _add_sql_comment, + _get_opentelemetry_values, +) +from opentelemetry.trace.propagation.tracecontext import ( + TraceContextTextMapPropagator, +) + +_propagator = TraceContextTextMapPropagator() + +_django_version = get_version() +_logger = getLogger(__name__) + +T = TypeVar("T") # pylint: disable-msg=invalid-name + + +class SqlCommenter: + """ + Middleware to append a comment to each database query with details about + the framework and the execution context. + """ + + def __init__(self, get_response) -> None: + self.get_response = get_response + + def __call__(self, request) -> Any: + with ExitStack() as stack: + for db_alias in connections: + stack.enter_context( + connections[db_alias].execute_wrapper( + _QueryWrapper(request) + ) + ) + return self.get_response(request) + + +class _QueryWrapper: + def __init__(self, request) -> None: + self.request = request + + def __call__(self, execute: Type[T], sql, params, many, context) -> T: + # pylint: disable-msg=too-many-locals + with_framework = getattr( + conf.settings, "SQLCOMMENTER_WITH_FRAMEWORK", True + ) + with_controller = getattr( + conf.settings, "SQLCOMMENTER_WITH_CONTROLLER", True + ) + with_route = getattr(conf.settings, "SQLCOMMENTER_WITH_ROUTE", True) + with_app_name = getattr( + conf.settings, "SQLCOMMENTER_WITH_APP_NAME", True + ) + with_opentelemetry = getattr( + conf.settings, "SQLCOMMENTER_WITH_OPENTELEMETRY", True + ) + with_db_driver = getattr( + conf.settings, "SQLCOMMENTER_WITH_DB_DRIVER", True + ) + + db_driver = context["connection"].settings_dict.get("ENGINE", "") + resolver_match = self.request.resolver_match + + sql = _add_sql_comment( + sql, + # Information about the controller. + controller=resolver_match.view_name + if resolver_match and with_controller + else None, + # route is the pattern that matched a request with a controller i.e. the regex + # See https://docs.djangoproject.com/en/stable/ref/urlresolvers/#django.urls.ResolverMatch.route + # getattr() because the attribute doesn't exist in Django < 2.2. + route=getattr(resolver_match, "route", None) + if resolver_match and with_route + else None, + # app_name is the application namespace for the URL pattern that matches the URL. + # See https://docs.djangoproject.com/en/stable/ref/urlresolvers/#django.urls.ResolverMatch.app_name + app_name=(resolver_match.app_name or None) + if resolver_match and with_app_name + else None, + # Framework centric information. + framework=f"django:{_django_version}" if with_framework else None, + # Information about the database and driver. + db_driver=db_driver if with_db_driver else None, + **_get_opentelemetry_values() if with_opentelemetry else {}, + ) + + # TODO: MySQL truncates logs > 1024B so prepend comments + # instead of statements, if the engine is MySQL. + # See: + # * https://github.com/basecamp/marginalia/issues/61 + # * https://github.com/basecamp/marginalia/pull/80 + + # Add the query to the query log if debugging. + if isinstance(context["cursor"], CursorDebugWrapper): + context["connection"].queries_log.append(sql) + + return execute(sql, params, many, context) diff --git a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/version.py b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/version.py +++ b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py b/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py index 855cf3e389..05457de43d 100644 --- a/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware.py @@ -35,7 +35,6 @@ 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,10 +83,16 @@ _django_instrumentor = DjangoInstrumentor() -class TestMiddleware(TestBase, WsgiTestBase): +class TestMiddleware(WsgiTestBase): @classmethod def setUpClass(cls): - conf.settings.configure(ROOT_URLCONF=modules[__name__]) + conf.settings.configure( + ROOT_URLCONF=modules[__name__], + DATABASES={ + "default": {}, + "other": {}, + }, # db.connections gets populated only at first test execution + ) super().setUpClass() def setUp(self): @@ -103,11 +108,11 @@ def setUp(self): ) self.env_patch.start() self.exclude_patch = patch( - "opentelemetry.instrumentation.django.middleware._DjangoMiddleware._excluded_urls", + "opentelemetry.instrumentation.django.middleware.otel_middleware._DjangoMiddleware._excluded_urls", get_excluded_urls("DJANGO"), ) self.traced_patch = patch( - "opentelemetry.instrumentation.django.middleware._DjangoMiddleware._traced_request_attrs", + "opentelemetry.instrumentation.django.middleware.otel_middleware._DjangoMiddleware._traced_request_attrs", get_traced_request_attrs("DJANGO"), ) self.exclude_patch.start() @@ -402,7 +407,7 @@ def test_trace_response_headers(self): self.memory_exporter.clear() -class TestMiddlewareWithTracerProvider(TestBase, WsgiTestBase): +class TestMiddlewareWithTracerProvider(WsgiTestBase): @classmethod def setUpClass(cls): conf.settings.configure(ROOT_URLCONF=modules[__name__]) @@ -460,7 +465,7 @@ def test_django_with_wsgi_instrumented(self): ) -class TestMiddlewareWsgiWithCustomHeaders(TestBase, WsgiTestBase): +class TestMiddlewareWsgiWithCustomHeaders(WsgiTestBase): @classmethod def setUpClass(cls): conf.settings.configure(ROOT_URLCONF=modules[__name__]) diff --git a/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware_asgi.py b/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware_asgi.py index 14a1ce82a9..941fda49bb 100644 --- a/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware_asgi.py +++ b/instrumentation/opentelemetry-instrumentation-django/tests/test_middleware_asgi.py @@ -104,11 +104,11 @@ def setUp(self): ) self.env_patch.start() self.exclude_patch = patch( - "opentelemetry.instrumentation.django.middleware._DjangoMiddleware._excluded_urls", + "opentelemetry.instrumentation.django.middleware.otel_middleware._DjangoMiddleware._excluded_urls", get_excluded_urls("DJANGO"), ) self.traced_patch = patch( - "opentelemetry.instrumentation.django.middleware._DjangoMiddleware._traced_request_attrs", + "opentelemetry.instrumentation.django.middleware.otel_middleware._DjangoMiddleware._traced_request_attrs", get_traced_request_attrs("DJANGO"), ) self.exclude_patch.start() diff --git a/instrumentation/opentelemetry-instrumentation-django/tests/test_sqlcommenter.py b/instrumentation/opentelemetry-instrumentation-django/tests/test_sqlcommenter.py new file mode 100644 index 0000000000..f9b8ed5233 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-django/tests/test_sqlcommenter.py @@ -0,0 +1,117 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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=no-name-in-module +from unittest.mock import MagicMock, patch + +import pytest +from django import VERSION, conf +from django.http import HttpResponse +from django.test.utils import setup_test_environment, teardown_test_environment + +from opentelemetry.instrumentation.django import DjangoInstrumentor +from opentelemetry.instrumentation.django.middleware.sqlcommenter_middleware import ( + SqlCommenter, + _QueryWrapper, +) +from opentelemetry.test.wsgitestutil import WsgiTestBase + +DJANGO_2_0 = VERSION >= (2, 0) + +_django_instrumentor = DjangoInstrumentor() + + +class TestMiddleware(WsgiTestBase): + @classmethod + def setUpClass(cls): + conf.settings.configure( + SQLCOMMENTER_WITH_FRAMEWORK=False, + SQLCOMMENTER_WITH_DB_DRIVER=False, + ) + super().setUpClass() + + def setUp(self): + super().setUp() + setup_test_environment() + _django_instrumentor.instrument(is_sql_commentor_enabled=True) + + def tearDown(self): + super().tearDown() + teardown_test_environment() + _django_instrumentor.uninstrument() + + @classmethod + def tearDownClass(cls): + super().tearDownClass() + conf.settings = conf.LazySettings() + + @patch( + "opentelemetry.instrumentation.django.middleware.sqlcommenter_middleware.SqlCommenter" + ) + def test_middleware_added(self, sqlcommenter_middleware): + instance = sqlcommenter_middleware.return_value + instance.get_response = HttpResponse() + if DJANGO_2_0: + middleware = conf.settings.MIDDLEWARE + else: + middleware = conf.settings.MIDDLEWARE_CLASSES + + self.assertTrue( + "opentelemetry.instrumentation.django.middleware.sqlcommenter_middleware.SqlCommenter" + in middleware + ) + + @patch( + "opentelemetry.instrumentation.django.middleware.sqlcommenter_middleware._get_opentelemetry_values" + ) + def test_query_wrapper(self, trace_capture): + requests_mock = MagicMock() + requests_mock.resolver_match.view_name = "view" + requests_mock.resolver_match.route = "route" + requests_mock.resolver_match.app_name = "app" + + trace_capture.return_value = { + "traceparent": "*traceparent='00-000000000000000000000000deadbeef-000000000000beef-00" + } + qw_instance = _QueryWrapper(requests_mock) + execute_mock_obj = MagicMock() + qw_instance( + execute_mock_obj, + "Select 1;", + MagicMock("test"), + MagicMock("test1"), + MagicMock(), + ) + output_sql = execute_mock_obj.call_args[0][0] + self.assertEqual( + output_sql, + "Select 1 /*app_name='app',controller='view',route='route',traceparent='%%2Atraceparent%%3D%%2700-0000000" + "00000000000000000deadbeef-000000000000beef-00'*/;", + ) + + @patch( + "opentelemetry.instrumentation.django.middleware.sqlcommenter_middleware._QueryWrapper" + ) + def test_multiple_connection_support(self, query_wrapper): + if not DJANGO_2_0: + pytest.skip() + + requests_mock = MagicMock() + get_response = MagicMock() + + sql_instance = SqlCommenter(get_response) + sql_instance(requests_mock) + + # check if query_wrapper is added to the context for 2 databases + self.assertEqual(query_wrapper.call_count, 2) diff --git a/instrumentation/opentelemetry-instrumentation-elasticsearch/setup.cfg b/instrumentation/opentelemetry-instrumentation-elasticsearch/setup.cfg index c98b37f7f1..4cd1963136 100644 --- a/instrumentation/opentelemetry-instrumentation-elasticsearch/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-elasticsearch/setup.cfg @@ -41,13 +41,13 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-semantic-conventions == 0.31b0 - opentelemetry-instrumentation == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 + opentelemetry-instrumentation == 0.32b0 wrapt >= 1.0.0, < 2.0.0 [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 elasticsearch-dsl >= 2.0 [options.packages.find] diff --git a/instrumentation/opentelemetry-instrumentation-elasticsearch/src/opentelemetry/instrumentation/elasticsearch/version.py b/instrumentation/opentelemetry-instrumentation-elasticsearch/src/opentelemetry/instrumentation/elasticsearch/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-elasticsearch/src/opentelemetry/instrumentation/elasticsearch/version.py +++ b/instrumentation/opentelemetry-instrumentation-elasticsearch/src/opentelemetry/instrumentation/elasticsearch/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-falcon/setup.cfg b/instrumentation/opentelemetry-instrumentation-falcon/setup.cfg index 57c888e66c..8759a58a46 100644 --- a/instrumentation/opentelemetry-instrumentation-falcon/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-falcon/setup.cfg @@ -40,16 +40,16 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-instrumentation-wsgi == 0.31b0 - opentelemetry-util-http == 0.31b0 - opentelemetry-instrumentation == 0.31b0 + opentelemetry-instrumentation-wsgi == 0.32b0 + opentelemetry-util-http == 0.32b0 + opentelemetry-instrumentation == 0.32b0 opentelemetry-api ~= 1.3 - opentelemetry-semantic-conventions == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 packaging >= 20.0 [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 parameterized == 0.7.4 [options.packages.find] diff --git a/instrumentation/opentelemetry-instrumentation-falcon/src/opentelemetry/instrumentation/falcon/version.py b/instrumentation/opentelemetry-instrumentation-falcon/src/opentelemetry/instrumentation/falcon/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-falcon/src/opentelemetry/instrumentation/falcon/version.py +++ b/instrumentation/opentelemetry-instrumentation-falcon/src/opentelemetry/instrumentation/falcon/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/setup.cfg b/instrumentation/opentelemetry-instrumentation-fastapi/setup.cfg index a7ffb994e4..35ba30254c 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-fastapi/setup.cfg @@ -41,10 +41,10 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-semantic-conventions == 0.31b0 - opentelemetry-instrumentation == 0.31b0 - opentelemetry-instrumentation-asgi == 0.31b0 - opentelemetry-util-http == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 + opentelemetry-instrumentation == 0.32b0 + opentelemetry-instrumentation-asgi == 0.32b0 + opentelemetry-util-http == 0.32b0 [options.entry_points] opentelemetry_instrumentor = @@ -52,7 +52,7 @@ opentelemetry_instrumentor = [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 requests ~= 2.23.0 # needed for testclient [options.packages.find] diff --git a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/version.py b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/version.py +++ b/instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-flask/setup.cfg b/instrumentation/opentelemetry-instrumentation-flask/setup.cfg index d6a2b0bde2..f8d48c1dd6 100644 --- a/instrumentation/opentelemetry-instrumentation-flask/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-flask/setup.cfg @@ -40,15 +40,15 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-util-http == 0.31b0 - opentelemetry-instrumentation == 0.31b0 - opentelemetry-instrumentation-wsgi == 0.31b0 + opentelemetry-util-http == 0.32b0 + opentelemetry-instrumentation == 0.32b0 + opentelemetry-instrumentation-wsgi == 0.32b0 opentelemetry-api ~= 1.3 - opentelemetry-semantic-conventions == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 markupsafe==2.0.1 [options.entry_points] diff --git a/instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/version.py b/instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/version.py +++ b/instrumentation/opentelemetry-instrumentation-flask/src/opentelemetry/instrumentation/flask/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-flask/tests/test_automatic.py b/instrumentation/opentelemetry-instrumentation-flask/tests/test_automatic.py index 4a64c0a9e0..b4ed9eb0bc 100644 --- a/instrumentation/opentelemetry-instrumentation-flask/tests/test_automatic.py +++ b/instrumentation/opentelemetry-instrumentation-flask/tests/test_automatic.py @@ -17,14 +17,13 @@ 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() diff --git a/instrumentation/opentelemetry-instrumentation-flask/tests/test_programmatic.py b/instrumentation/opentelemetry-instrumentation-flask/tests/test_programmatic.py index 6329bf1d30..2bcb097c7b 100644 --- a/instrumentation/opentelemetry-instrumentation-flask/tests/test_programmatic.py +++ b/instrumentation/opentelemetry-instrumentation-flask/tests/test_programmatic.py @@ -26,7 +26,6 @@ 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 @@ def test_exclude_lists_from_explicit(self): self.assertEqual(len(span_list), 1) -class TestProgrammaticHooks(InstrumentationTest, TestBase, WsgiTestBase): +class TestProgrammaticHooks(InstrumentationTest, WsgiTestBase): def setUp(self): super().setUp() @@ -300,9 +299,7 @@ def test_hooks(self): 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 @@ def test_no_app_hooks(self): 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 @@ def test_custom_span_name(self): class TestProgrammaticCustomTracerProviderWithoutApp( - InstrumentationTest, TestBase, WsgiTestBase + InstrumentationTest, WsgiTestBase ): def setUp(self): super().setUp() @@ -417,7 +412,7 @@ def test_custom_span_name(self): class TestProgrammaticWrappedWithOtherFramework( - InstrumentationTest, TestBase, WsgiTestBase + InstrumentationTest, WsgiTestBase ): def setUp(self): super().setUp() @@ -444,9 +439,7 @@ def test_mark_span_internal_in_presence_of_span_from_other_framework(self): ) -class TestCustomRequestResponseHeaders( - InstrumentationTest, TestBase, WsgiTestBase -): +class TestCustomRequestResponseHeaders(InstrumentationTest, WsgiTestBase): def setUp(self): super().setUp() diff --git a/instrumentation/opentelemetry-instrumentation-grpc/setup.cfg b/instrumentation/opentelemetry-instrumentation-grpc/setup.cfg index 375b56bf5a..b6daab3a47 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-grpc/setup.cfg @@ -42,13 +42,13 @@ packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 opentelemetry-sdk ~= 1.3 - opentelemetry-semantic-conventions == 0.31b0 - opentelemetry-instrumentation == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 + opentelemetry-instrumentation == 0.32b0 wrapt >= 1.0.0, < 2.0.0 [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 opentelemetry-sdk ~= 1.3 protobuf ~= 3.13 diff --git a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/version.py b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/version.py +++ b/instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-httpx/setup.cfg b/instrumentation/opentelemetry-instrumentation-httpx/setup.cfg index 067a4610e7..66c7a23a7d 100644 --- a/instrumentation/opentelemetry-instrumentation-httpx/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-httpx/setup.cfg @@ -41,13 +41,13 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-instrumentation == 0.31b0 - opentelemetry-semantic-conventions == 0.31b0 + opentelemetry-instrumentation == 0.32b0 + opentelemetry-semantic-conventions == 0.32b0 [options.extras_require] test = opentelemetry-sdk ~= 1.3 - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 [options.packages.find] where = src diff --git a/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/version.py b/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/version.py +++ b/instrumentation/opentelemetry-instrumentation-httpx/src/opentelemetry/instrumentation/httpx/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-jinja2/setup.cfg b/instrumentation/opentelemetry-instrumentation-jinja2/setup.cfg index 7acfa13bd6..94ca73214e 100644 --- a/instrumentation/opentelemetry-instrumentation-jinja2/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-jinja2/setup.cfg @@ -40,12 +40,12 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-instrumentation == 0.31b0 + opentelemetry-instrumentation == 0.32b0 wrapt >= 1.0.0, < 2.0.0 [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 markupsafe==2.0.1 [options.packages.find] diff --git a/instrumentation/opentelemetry-instrumentation-jinja2/src/opentelemetry/instrumentation/jinja2/version.py b/instrumentation/opentelemetry-instrumentation-jinja2/src/opentelemetry/instrumentation/jinja2/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-jinja2/src/opentelemetry/instrumentation/jinja2/version.py +++ b/instrumentation/opentelemetry-instrumentation-jinja2/src/opentelemetry/instrumentation/jinja2/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-kafka-python/setup.cfg b/instrumentation/opentelemetry-instrumentation-kafka-python/setup.cfg index 4af2926b21..0e80c66a82 100644 --- a/instrumentation/opentelemetry-instrumentation-kafka-python/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-kafka-python/setup.cfg @@ -41,13 +41,13 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.5 - opentelemetry-instrumentation == 0.31b0 - opentelemetry-semantic-conventions == 0.31b0 + opentelemetry-instrumentation == 0.32b0 + opentelemetry-semantic-conventions == 0.32b0 [options.extras_require] test = wrapt >= 1.0.0, < 2.0.0 - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 [options.entry_points] opentelemetry_instrumentor = diff --git a/instrumentation/opentelemetry-instrumentation-kafka-python/src/opentelemetry/instrumentation/kafka/version.py b/instrumentation/opentelemetry-instrumentation-kafka-python/src/opentelemetry/instrumentation/kafka/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-kafka-python/src/opentelemetry/instrumentation/kafka/version.py +++ b/instrumentation/opentelemetry-instrumentation-kafka-python/src/opentelemetry/instrumentation/kafka/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-logging/setup.cfg b/instrumentation/opentelemetry-instrumentation-logging/setup.cfg index b51e281175..9d423eff5e 100644 --- a/instrumentation/opentelemetry-instrumentation-logging/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-logging/setup.cfg @@ -40,11 +40,11 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-instrumentation == 0.31b0 + opentelemetry-instrumentation == 0.32b0 [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 [options.packages.find] where = src diff --git a/instrumentation/opentelemetry-instrumentation-logging/src/opentelemetry/instrumentation/logging/version.py b/instrumentation/opentelemetry-instrumentation-logging/src/opentelemetry/instrumentation/logging/version.py index ca9e66477d..d0074fee82 100644 --- a/instrumentation/opentelemetry-instrumentation-logging/src/opentelemetry/instrumentation/logging/version.py +++ b/instrumentation/opentelemetry-instrumentation-logging/src/opentelemetry/instrumentation/logging/version.py @@ -12,6 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" _instruments = tuple() diff --git a/instrumentation/opentelemetry-instrumentation-mysql/setup.cfg b/instrumentation/opentelemetry-instrumentation-mysql/setup.cfg index ebc00b224d..6807e48896 100644 --- a/instrumentation/opentelemetry-instrumentation-mysql/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-mysql/setup.cfg @@ -41,12 +41,12 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-instrumentation-dbapi == 0.31b0 - opentelemetry-instrumentation == 0.31b0 + opentelemetry-instrumentation-dbapi == 0.32b0 + opentelemetry-instrumentation == 0.32b0 [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 [options.packages.find] where = src diff --git a/instrumentation/opentelemetry-instrumentation-mysql/src/opentelemetry/instrumentation/mysql/version.py b/instrumentation/opentelemetry-instrumentation-mysql/src/opentelemetry/instrumentation/mysql/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-mysql/src/opentelemetry/instrumentation/mysql/version.py +++ b/instrumentation/opentelemetry-instrumentation-mysql/src/opentelemetry/instrumentation/mysql/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-pika/setup.cfg b/instrumentation/opentelemetry-instrumentation-pika/setup.cfg index b20a270963..98ddc6f173 100644 --- a/instrumentation/opentelemetry-instrumentation-pika/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-pika/setup.cfg @@ -49,7 +49,7 @@ install_requires = test = pytest wrapt >= 1.0.0, < 2.0.0 - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 [options.packages.find] where = src diff --git a/instrumentation/opentelemetry-instrumentation-pika/src/opentelemetry/instrumentation/pika/version.py b/instrumentation/opentelemetry-instrumentation-pika/src/opentelemetry/instrumentation/pika/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-pika/src/opentelemetry/instrumentation/pika/version.py +++ b/instrumentation/opentelemetry-instrumentation-pika/src/opentelemetry/instrumentation/pika/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-psycopg2/setup.cfg b/instrumentation/opentelemetry-instrumentation-psycopg2/setup.cfg index 5e97b17835..c26abdbe86 100644 --- a/instrumentation/opentelemetry-instrumentation-psycopg2/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-psycopg2/setup.cfg @@ -41,12 +41,12 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-instrumentation-dbapi == 0.31b0 - opentelemetry-instrumentation == 0.31b0 + opentelemetry-instrumentation-dbapi == 0.32b0 + opentelemetry-instrumentation == 0.32b0 [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 [options.packages.find] where = src diff --git a/instrumentation/opentelemetry-instrumentation-psycopg2/src/opentelemetry/instrumentation/psycopg2/version.py b/instrumentation/opentelemetry-instrumentation-psycopg2/src/opentelemetry/instrumentation/psycopg2/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-psycopg2/src/opentelemetry/instrumentation/psycopg2/version.py +++ b/instrumentation/opentelemetry-instrumentation-psycopg2/src/opentelemetry/instrumentation/psycopg2/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-psycopg2/tests/test_psycopg2_integration.py b/instrumentation/opentelemetry-instrumentation-psycopg2/tests/test_psycopg2_integration.py index e974da7d60..f516a07882 100644 --- a/instrumentation/opentelemetry-instrumentation-psycopg2/tests/test_psycopg2_integration.py +++ b/instrumentation/opentelemetry-instrumentation-psycopg2/tests/test_psycopg2_integration.py @@ -68,6 +68,7 @@ def get_dsn_parameters(self): # pylint: disable=no-self-use class TestPostgresqlIntegration(TestBase): def setUp(self): + super().setUp() self.cursor_mock = mock.patch( "opentelemetry.instrumentation.psycopg2.pg_cursor", MockCursor ) diff --git a/instrumentation/opentelemetry-instrumentation-pymemcache/setup.cfg b/instrumentation/opentelemetry-instrumentation-pymemcache/setup.cfg index b810608501..b2c14aa1a9 100644 --- a/instrumentation/opentelemetry-instrumentation-pymemcache/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-pymemcache/setup.cfg @@ -41,13 +41,13 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-semantic-conventions == 0.31b0 - opentelemetry-instrumentation == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 + opentelemetry-instrumentation == 0.32b0 wrapt >= 1.0.0, < 2.0.0 [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 [options.packages.find] where = src diff --git a/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/version.py b/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/version.py +++ b/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-pymongo/setup.cfg b/instrumentation/opentelemetry-instrumentation-pymongo/setup.cfg index 4bb0ce7036..8fcedf2742 100644 --- a/instrumentation/opentelemetry-instrumentation-pymongo/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-pymongo/setup.cfg @@ -41,12 +41,12 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-semantic-conventions == 0.31b0 - opentelemetry-instrumentation == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 + opentelemetry-instrumentation == 0.32b0 [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 [options.packages.find] where = src diff --git a/instrumentation/opentelemetry-instrumentation-pymongo/src/opentelemetry/instrumentation/pymongo/__init__.py b/instrumentation/opentelemetry-instrumentation-pymongo/src/opentelemetry/instrumentation/pymongo/__init__.py index 171979dbfe..ba3afae11f 100644 --- a/instrumentation/opentelemetry-instrumentation-pymongo/src/opentelemetry/instrumentation/pymongo/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-pymongo/src/opentelemetry/instrumentation/pymongo/__init__.py @@ -223,7 +223,6 @@ def _instrument(self, **kwargs): request_hook = kwargs.get("request_hook", dummy_callback) response_hook = kwargs.get("response_hook", dummy_callback) failed_hook = kwargs.get("failed_hook", dummy_callback) - # Create and register a CommandTracer only the first time if self._commandtracer_instance is None: tracer = get_tracer(__name__, __version__, tracer_provider) @@ -235,7 +234,6 @@ def _instrument(self, **kwargs): failed_hook=failed_hook, ) monitoring.register(self._commandtracer_instance) - # If already created, just enable it self._commandtracer_instance.is_enabled = True diff --git a/instrumentation/opentelemetry-instrumentation-pymongo/src/opentelemetry/instrumentation/pymongo/version.py b/instrumentation/opentelemetry-instrumentation-pymongo/src/opentelemetry/instrumentation/pymongo/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-pymongo/src/opentelemetry/instrumentation/pymongo/version.py +++ b/instrumentation/opentelemetry-instrumentation-pymongo/src/opentelemetry/instrumentation/pymongo/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-pymysql/setup.cfg b/instrumentation/opentelemetry-instrumentation-pymysql/setup.cfg index f13ea54586..3c31d32b62 100644 --- a/instrumentation/opentelemetry-instrumentation-pymysql/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-pymysql/setup.cfg @@ -41,12 +41,12 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-instrumentation-dbapi == 0.31b0 - opentelemetry-instrumentation == 0.31b0 + opentelemetry-instrumentation-dbapi == 0.32b0 + opentelemetry-instrumentation == 0.32b0 [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 [options.packages.find] where = src diff --git a/instrumentation/opentelemetry-instrumentation-pymysql/src/opentelemetry/instrumentation/pymysql/version.py b/instrumentation/opentelemetry-instrumentation-pymysql/src/opentelemetry/instrumentation/pymysql/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-pymysql/src/opentelemetry/instrumentation/pymysql/version.py +++ b/instrumentation/opentelemetry-instrumentation-pymysql/src/opentelemetry/instrumentation/pymysql/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-pyramid/setup.cfg b/instrumentation/opentelemetry-instrumentation-pyramid/setup.cfg index 45e8c02735..d380dc5b7d 100644 --- a/instrumentation/opentelemetry-instrumentation-pyramid/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-pyramid/setup.cfg @@ -40,17 +40,17 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-instrumentation == 0.31b0 + opentelemetry-instrumentation == 0.32b0 opentelemetry-api ~= 1.3 - opentelemetry-semantic-conventions == 0.31b0 - opentelemetry-instrumentation-wsgi == 0.31b0 - opentelemetry-util-http == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 + opentelemetry-instrumentation-wsgi == 0.32b0 + opentelemetry-util-http == 0.32b0 wrapt >= 1.0.0, < 2.0.0 [options.extras_require] test = werkzeug == 0.16.1 - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 [options.packages.find] where = src diff --git a/instrumentation/opentelemetry-instrumentation-pyramid/src/opentelemetry/instrumentation/pyramid/version.py b/instrumentation/opentelemetry-instrumentation-pyramid/src/opentelemetry/instrumentation/pyramid/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-pyramid/src/opentelemetry/instrumentation/pyramid/version.py +++ b/instrumentation/opentelemetry-instrumentation-pyramid/src/opentelemetry/instrumentation/pyramid/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-pyramid/tests/test_automatic.py b/instrumentation/opentelemetry-instrumentation-pyramid/tests/test_automatic.py index b9edd44d72..89df49e49e 100644 --- a/instrumentation/opentelemetry-instrumentation-pyramid/tests/test_automatic.py +++ b/instrumentation/opentelemetry-instrumentation-pyramid/tests/test_automatic.py @@ -19,7 +19,6 @@ 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 .pyramid_base_test import InstrumentationTest -class TestAutomatic(InstrumentationTest, TestBase, WsgiTestBase): +class TestAutomatic(InstrumentationTest, WsgiTestBase): def setUp(self): super().setUp() @@ -158,9 +157,7 @@ def test_400s_response_is_not_an_error(self): 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 @@ def test_with_existing_span(self): ) -class TestCustomRequestResponseHeaders( - InstrumentationTest, TestBase, WsgiTestBase -): +class TestCustomRequestResponseHeaders(InstrumentationTest, WsgiTestBase): def setUp(self): super().setUp() PyramidInstrumentor().instrument() @@ -296,9 +291,7 @@ def test_custom_response_header_not_added_in_internal_span(self): 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 diff --git a/instrumentation/opentelemetry-instrumentation-pyramid/tests/test_programmatic.py b/instrumentation/opentelemetry-instrumentation-pyramid/tests/test_programmatic.py index 414e6ff28e..d3a4fa91db 100644 --- a/instrumentation/opentelemetry-instrumentation-pyramid/tests/test_programmatic.py +++ b/instrumentation/opentelemetry-instrumentation-pyramid/tests/test_programmatic.py @@ -24,7 +24,6 @@ ) 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() diff --git a/instrumentation/opentelemetry-instrumentation-redis/setup.cfg b/instrumentation/opentelemetry-instrumentation-redis/setup.cfg index 3123ea0b3f..e0bd1858f4 100644 --- a/instrumentation/opentelemetry-instrumentation-redis/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-redis/setup.cfg @@ -41,13 +41,13 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-semantic-conventions == 0.31b0 - opentelemetry-instrumentation == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 + opentelemetry-instrumentation == 0.32b0 wrapt >= 1.12.1 [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 opentelemetry-sdk ~= 1.3 [options.packages.find] diff --git a/instrumentation/opentelemetry-instrumentation-redis/src/opentelemetry/instrumentation/redis/__init__.py b/instrumentation/opentelemetry-instrumentation-redis/src/opentelemetry/instrumentation/redis/__init__.py index 8aaec87952..4db5523266 100644 --- a/instrumentation/opentelemetry-instrumentation-redis/src/opentelemetry/instrumentation/redis/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-redis/src/opentelemetry/instrumentation/redis/__init__.py @@ -122,9 +122,12 @@ def response_hook(span, instance, response): if redis.VERSION >= _REDIS_ASYNCIO_VERSION: import redis.asyncio +_REDIS_CLUSTER_VERSION = (4, 1, 0) +_REDIS_ASYNCIO_CLUSTER_VERSION = (4, 3, 0) + def _set_connection_attributes(span, conn): - if not span.is_recording(): + if not span.is_recording() or not hasattr(conn, "connection_pool"): return for key, value in _extract_conn_attributes( conn.connection_pool.connection_kwargs @@ -159,10 +162,29 @@ def _traced_execute_command(func, instance, args, kwargs): return response def _traced_execute_pipeline(func, instance, args, kwargs): - cmds = [_format_command_args(c) for c, _ in instance.command_stack] - resource = "\n".join(cmds) + try: + command_stack = ( + instance.command_stack + if hasattr(instance, "command_stack") + else instance._command_stack + ) - span_name = " ".join([args[0] for args, _ in instance.command_stack]) + cmds = [ + _format_command_args(c.args if hasattr(c, "args") else c[0]) + for c in command_stack + ] + resource = "\n".join(cmds) + + span_name = " ".join( + [ + (c.args[0] if hasattr(c, "args") else c[0][0]) + for c in command_stack + ] + ) + except (AttributeError, IndexError): + command_stack = [] + resource = "" + span_name = "" with tracer.start_as_current_span( span_name, kind=trace.SpanKind.CLIENT @@ -171,7 +193,7 @@ def _traced_execute_pipeline(func, instance, args, kwargs): span.set_attribute(SpanAttributes.DB_STATEMENT, resource) _set_connection_attributes(span, instance) span.set_attribute( - "db.redis.pipeline_length", len(instance.command_stack) + "db.redis.pipeline_length", len(command_stack) ) response = func(*args, **kwargs) if callable(response_hook): @@ -196,6 +218,17 @@ def _traced_execute_pipeline(func, instance, args, kwargs): f"{pipeline_class}.immediate_execute_command", _traced_execute_command, ) + if redis.VERSION >= _REDIS_CLUSTER_VERSION: + wrap_function_wrapper( + "redis.cluster", + "RedisCluster.execute_command", + _traced_execute_command, + ) + wrap_function_wrapper( + "redis.cluster", + "ClusterPipeline.execute", + _traced_execute_pipeline, + ) if redis.VERSION >= _REDIS_ASYNCIO_VERSION: wrap_function_wrapper( "redis.asyncio", @@ -212,6 +245,17 @@ def _traced_execute_pipeline(func, instance, args, kwargs): f"{pipeline_class}.immediate_execute_command", _traced_execute_command, ) + if redis.VERSION >= _REDIS_ASYNCIO_CLUSTER_VERSION: + wrap_function_wrapper( + "redis.asyncio.cluster", + "RedisCluster.execute_command", + _traced_execute_command, + ) + wrap_function_wrapper( + "redis.asyncio.cluster", + "ClusterPipeline.execute", + _traced_execute_pipeline, + ) class RedisInstrumentor(BaseInstrumentor): @@ -258,8 +302,14 @@ def _uninstrument(self, **kwargs): unwrap(redis.Redis, "pipeline") unwrap(redis.client.Pipeline, "execute") unwrap(redis.client.Pipeline, "immediate_execute_command") + if redis.VERSION >= _REDIS_CLUSTER_VERSION: + unwrap(redis.cluster.RedisCluster, "execute_command") + unwrap(redis.cluster.ClusterPipeline, "execute") if redis.VERSION >= _REDIS_ASYNCIO_VERSION: unwrap(redis.asyncio.Redis, "execute_command") unwrap(redis.asyncio.Redis, "pipeline") unwrap(redis.asyncio.client.Pipeline, "execute") unwrap(redis.asyncio.client.Pipeline, "immediate_execute_command") + if redis.VERSION >= _REDIS_ASYNCIO_CLUSTER_VERSION: + unwrap(redis.asyncio.cluster.RedisCluster, "execute_command") + unwrap(redis.asyncio.cluster.ClusterPipeline, "execute") diff --git a/instrumentation/opentelemetry-instrumentation-redis/src/opentelemetry/instrumentation/redis/version.py b/instrumentation/opentelemetry-instrumentation-redis/src/opentelemetry/instrumentation/redis/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-redis/src/opentelemetry/instrumentation/redis/version.py +++ b/instrumentation/opentelemetry-instrumentation-redis/src/opentelemetry/instrumentation/redis/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-redis/tests/test_redis.py b/instrumentation/opentelemetry-instrumentation-redis/tests/test_redis.py index 3780f0c245..3d5479e731 100644 --- a/instrumentation/opentelemetry-instrumentation-redis/tests/test_redis.py +++ b/instrumentation/opentelemetry-instrumentation-redis/tests/test_redis.py @@ -21,9 +21,16 @@ class TestRedis(TestBase): + def setUp(self): + super().setUp() + RedisInstrumentor().instrument(tracer_provider=self.tracer_provider) + + def tearDown(self): + super().tearDown() + RedisInstrumentor().uninstrument() + def test_span_properties(self): redis_client = redis.Redis() - RedisInstrumentor().instrument(tracer_provider=self.tracer_provider) with mock.patch.object(redis_client, "connection"): redis_client.get("key") @@ -36,7 +43,6 @@ def test_span_properties(self): def test_not_recording(self): redis_client = redis.Redis() - RedisInstrumentor().instrument(tracer_provider=self.tracer_provider) mock_tracer = mock.Mock() mock_span = mock.Mock() @@ -53,7 +59,6 @@ def test_not_recording(self): def test_instrument_uninstrument(self): redis_client = redis.Redis() - RedisInstrumentor().instrument(tracer_provider=self.tracer_provider) with mock.patch.object(redis_client, "connection"): redis_client.get("key") diff --git a/instrumentation/opentelemetry-instrumentation-remoulade/setup.cfg b/instrumentation/opentelemetry-instrumentation-remoulade/setup.cfg index e4fe1fe000..a70dd83781 100644 --- a/instrumentation/opentelemetry-instrumentation-remoulade/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-remoulade/setup.cfg @@ -40,12 +40,12 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.10 - opentelemetry-semantic-conventions == 0.31b0 - opentelemetry-instrumentation == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 + opentelemetry-instrumentation == 0.32b0 [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 opentelemetry-sdk ~= 1.10 [options.packages.find] diff --git a/instrumentation/opentelemetry-instrumentation-remoulade/src/opentelemetry/instrumentation/remoulade/version.py b/instrumentation/opentelemetry-instrumentation-remoulade/src/opentelemetry/instrumentation/remoulade/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-remoulade/src/opentelemetry/instrumentation/remoulade/version.py +++ b/instrumentation/opentelemetry-instrumentation-remoulade/src/opentelemetry/instrumentation/remoulade/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-remoulade/tests/test_messages.py b/instrumentation/opentelemetry-instrumentation-remoulade/tests/test_messages.py index 4704111bdd..ff25b3f6de 100644 --- a/instrumentation/opentelemetry-instrumentation-remoulade/tests/test_messages.py +++ b/instrumentation/opentelemetry-instrumentation-remoulade/tests/test_messages.py @@ -35,6 +35,10 @@ def setUp(self): broker.declare_actor(actor_div) + def tearDown(self): + RemouladeInstrumentor().uninstrument() + super().tearDown() + def test_message(self): actor_div.send(2, 3) diff --git a/instrumentation/opentelemetry-instrumentation-requests/setup.cfg b/instrumentation/opentelemetry-instrumentation-requests/setup.cfg index 800f16178b..074978efad 100644 --- a/instrumentation/opentelemetry-instrumentation-requests/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-requests/setup.cfg @@ -41,13 +41,13 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-semantic-conventions == 0.31b0 - opentelemetry-instrumentation == 0.31b0 - opentelemetry-util-http == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 + opentelemetry-instrumentation == 0.32b0 + opentelemetry-util-http == 0.32b0 [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 httpretty ~= 1.0 [options.packages.find] diff --git a/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py b/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py index bd14c504e6..38e593a094 100644 --- a/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py @@ -50,13 +50,18 @@ import functools import types -from typing import Collection +from timeit import default_timer +from typing import Callable, Collection, Iterable, Optional +from urllib.parse import urlparse from requests.models import Response from requests.sessions import Session from requests.structures import CaseInsensitiveDict from opentelemetry import context + +# FIXME: fix the importing of this private attribute when the location of the _SUPPRESS_HTTP_INSTRUMENTATION_KEY is defined. +from opentelemetry.context import _SUPPRESS_HTTP_INSTRUMENTATION_KEY from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.instrumentation.requests.package import _instruments from opentelemetry.instrumentation.requests.version import __version__ @@ -64,9 +69,11 @@ _SUPPRESS_INSTRUMENTATION_KEY, http_status_to_status_code, ) +from opentelemetry.metrics import Histogram, get_meter from opentelemetry.propagate import inject from opentelemetry.semconv.trace import SpanAttributes -from opentelemetry.trace import SpanKind, get_tracer +from opentelemetry.trace import SpanKind, Tracer, get_tracer +from opentelemetry.trace.span import Span from opentelemetry.trace.status import Status from opentelemetry.util.http import ( get_excluded_urls, @@ -75,19 +82,17 @@ ) from opentelemetry.util.http.httplib import set_ip_on_next_http_connection -# A key to a context variable to avoid creating duplicate spans when instrumenting -# both, Session.request and Session.send, since Session.request calls into Session.send -_SUPPRESS_HTTP_INSTRUMENTATION_KEY = context.create_key( - "suppress_http_instrumentation" -) - _excluded_urls_from_env = get_excluded_urls("REQUESTS") # pylint: disable=unused-argument # pylint: disable=R0915 def _instrument( - tracer, span_callback=None, name_callback=None, excluded_urls=None + tracer: Tracer, + duration_histogram: Histogram, + span_callback: Optional[Callable[[Span, Response], str]] = None, + name_callback: Optional[Callable[[str, str], str]] = None, + excluded_urls: Iterable[str] = None, ): """Enables tracing of all requests calls that go through :code:`requests.session.Session.request` (this includes @@ -143,6 +148,7 @@ def call_wrapped(): request.method, request.url, call_wrapped, get_or_create_headers ) + # pylint: disable-msg=too-many-locals,too-many-branches def _instrumented_requests_call( method: str, url: str, call_wrapped, get_or_create_headers ): @@ -167,6 +173,23 @@ def _instrumented_requests_call( SpanAttributes.HTTP_URL: url, } + metric_labels = { + SpanAttributes.HTTP_METHOD: method, + } + + try: + parsed_url = urlparse(url) + metric_labels[SpanAttributes.HTTP_SCHEME] = parsed_url.scheme + if parsed_url.hostname: + metric_labels[SpanAttributes.HTTP_HOST] = parsed_url.hostname + metric_labels[ + SpanAttributes.NET_PEER_NAME + ] = parsed_url.hostname + if parsed_url.port: + metric_labels[SpanAttributes.NET_PEER_PORT] = parsed_url.port + except ValueError: + pass + with tracer.start_as_current_span( span_name, kind=SpanKind.CLIENT, attributes=span_attributes ) as span, set_ip_on_next_http_connection(span): @@ -178,12 +201,18 @@ def _instrumented_requests_call( token = context.attach( context.set_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY, True) ) + + start_time = default_timer() + try: result = call_wrapped() # *** PROCEED except Exception as exc: # pylint: disable=W0703 exception = exc result = getattr(exc, "response", None) finally: + elapsed_time = max( + round((default_timer() - start_time) * 1000), 0 + ) context.detach(token) if isinstance(result, Response): @@ -194,9 +223,23 @@ def _instrumented_requests_call( span.set_status( Status(http_status_to_status_code(result.status_code)) ) + + metric_labels[ + SpanAttributes.HTTP_STATUS_CODE + ] = result.status_code + + if result.raw is not None: + version = getattr(result.raw, "version", None) + if version: + metric_labels[SpanAttributes.HTTP_FLAVOR] = ( + "1.1" if version == 11 else "1.0" + ) + if span_callback is not None: span_callback(span, result) + duration_histogram.record(elapsed_time, attributes=metric_labels) + if exception is not None: raise exception.with_traceback(exception.__traceback__) @@ -261,8 +304,20 @@ def _instrument(self, **kwargs): tracer_provider = kwargs.get("tracer_provider") tracer = get_tracer(__name__, __version__, tracer_provider) excluded_urls = kwargs.get("excluded_urls") + meter_provider = kwargs.get("meter_provider") + meter = get_meter( + __name__, + __version__, + meter_provider, + ) + duration_histogram = meter.create_histogram( + name="http.client.duration", + unit="ms", + description="measures the duration of the outbound HTTP request", + ) _instrument( tracer, + duration_histogram, span_callback=kwargs.get("span_callback"), name_callback=kwargs.get("name_callback"), excluded_urls=_excluded_urls_from_env diff --git a/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/package.py b/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/package.py index c46bd6a2bf..8424bfeb2a 100644 --- a/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/package.py +++ b/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/package.py @@ -14,3 +14,5 @@ _instruments = ("requests ~= 2.0",) + +_supports_metrics = True diff --git a/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/version.py b/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/version.py +++ b/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-requests/tests/test_requests_integration.py b/instrumentation/opentelemetry-instrumentation-requests/tests/test_requests_integration.py index 1375333b67..790a2a7d03 100644 --- a/instrumentation/opentelemetry-instrumentation-requests/tests/test_requests_integration.py +++ b/instrumentation/opentelemetry-instrumentation-requests/tests/test_requests_integration.py @@ -22,6 +22,9 @@ import opentelemetry.instrumentation.requests from opentelemetry import context, trace + +# FIXME: fix the importing of this private attribute when the location of the _SUPPRESS_HTTP_INSTRUMENTATION_KEY is defined. +from opentelemetry.context import _SUPPRESS_HTTP_INSTRUMENTATION_KEY from opentelemetry.instrumentation.requests import RequestsInstrumentor from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY from opentelemetry.propagate import get_global_textmap, set_global_textmap @@ -246,6 +249,18 @@ def test_suppress_instrumentation(self): self.assert_span(num_spans=0) + def test_suppress_http_instrumentation(self): + token = context.attach( + context.set_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY, True) + ) + try: + result = self.perform_request(self.URL) + self.assertEqual(result.text, "Hello!") + finally: + context.detach(token) + + self.assert_span(num_spans=0) + def test_not_recording(self): with mock.patch("opentelemetry.trace.INVALID_SPAN") as mock_span: RequestsInstrumentor().uninstrument() @@ -472,3 +487,47 @@ def perform_request(url: str, session: requests.Session = None): request = requests.Request("GET", url) prepared_request = session.prepare_request(request) return session.send(prepared_request) + + +class TestRequestsIntergrationMetric(TestBase): + URL = "http://examplehost:8000/status/200" + + def setUp(self): + super().setUp() + RequestsInstrumentor().instrument(meter_provider=self.meter_provider) + + httpretty.enable() + httpretty.register_uri(httpretty.GET, self.URL, body="Hello!") + + def tearDown(self): + super().tearDown() + RequestsInstrumentor().uninstrument() + httpretty.disable() + + @staticmethod + def perform_request(url: str) -> requests.Response: + return requests.get(url) + + def test_basic_metric_success(self): + self.perform_request(self.URL) + + expected_attributes = { + "http.status_code": 200, + "http.host": "examplehost", + "net.peer.port": 8000, + "net.peer.name": "examplehost", + "http.method": "GET", + "http.flavor": "1.1", + "http.scheme": "http", + } + + for ( + resource_metrics + ) in self.memory_metrics_reader.get_metrics_data().resource_metrics: + for scope_metrics in resource_metrics.scope_metrics: + for metric in scope_metrics.metrics: + for data_point in metric.data.data_points: + self.assertDictEqual( + expected_attributes, dict(data_point.attributes) + ) + self.assertEqual(data_point.count, 1) diff --git a/instrumentation/opentelemetry-instrumentation-sklearn/setup.cfg b/instrumentation/opentelemetry-instrumentation-sklearn/setup.cfg index b535e424ba..ce37af0098 100644 --- a/instrumentation/opentelemetry-instrumentation-sklearn/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-sklearn/setup.cfg @@ -41,11 +41,11 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-instrumentation == 0.31b0 + opentelemetry-instrumentation == 0.32b0 [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 [options.packages.find] where = src diff --git a/instrumentation/opentelemetry-instrumentation-sklearn/src/opentelemetry/instrumentation/sklearn/version.py b/instrumentation/opentelemetry-instrumentation-sklearn/src/opentelemetry/instrumentation/sklearn/version.py index abd20005df..0eb5bcc268 100644 --- a/instrumentation/opentelemetry-instrumentation-sklearn/src/opentelemetry/instrumentation/sklearn/version.py +++ b/instrumentation/opentelemetry-instrumentation-sklearn/src/opentelemetry/instrumentation/sklearn/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-sqlalchemy/setup.cfg b/instrumentation/opentelemetry-instrumentation-sqlalchemy/setup.cfg index 0e1749260f..fcc7ad333e 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/setup.cfg @@ -41,8 +41,8 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-semantic-conventions == 0.31b0 - opentelemetry-instrumentation == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 + opentelemetry-instrumentation == 0.32b0 packaging >= 21.0 wrapt >= 1.11.2 diff --git a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/__init__.py b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/__init__.py index 0e1d7b266a..bc438c609a 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/__init__.py @@ -56,12 +56,14 @@ import sqlalchemy from packaging.version import parse as parse_version +from sqlalchemy.engine.base import Engine from wrapt import wrap_function_wrapper as _w from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.instrumentation.sqlalchemy.engine import ( EngineTracer, _get_tracer, + _wrap_connect, _wrap_create_async_engine, _wrap_create_engine, ) @@ -97,13 +99,17 @@ def _instrument(self, **kwargs): "create_engine", _wrap_create_engine(tracer_provider), ) + _w( + "sqlalchemy.engine.base", + "Engine.connect", + _wrap_connect(tracer_provider), + ) if parse_version(sqlalchemy.__version__).release >= (1, 4): _w( "sqlalchemy.ext.asyncio", "create_async_engine", _wrap_create_async_engine(tracer_provider), ) - if kwargs.get("engine") is not None: return EngineTracer( _get_tracer(tracer_provider), @@ -127,5 +133,6 @@ def _instrument(self, **kwargs): def _uninstrument(self, **kwargs): unwrap(sqlalchemy, "create_engine") unwrap(sqlalchemy.engine, "create_engine") + unwrap(Engine, "connect") if parse_version(sqlalchemy.__version__).release >= (1, 4): unwrap(sqlalchemy.ext.asyncio, "create_async_engine") diff --git a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py index 910ff7c4f6..25e8791dc8 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py @@ -21,11 +21,10 @@ ) from opentelemetry.instrumentation.sqlalchemy.version import __version__ from opentelemetry.instrumentation.utils import ( - _generate_opentelemetry_traceparent, - _generate_sql_comment, + _add_sql_comment, + _get_opentelemetry_values, ) from opentelemetry.semconv.trace import NetTransportValues, SpanAttributes -from opentelemetry.trace import Span from opentelemetry.trace.status import Status, StatusCode @@ -77,6 +76,23 @@ def _wrap_create_engine_internal(func, module, args, kwargs): return _wrap_create_engine_internal +def _wrap_connect(tracer_provider=None): + tracer = trace.get_tracer( + _instrumenting_module_name, + __version__, + tracer_provider=tracer_provider, + ) + + # pylint: disable=unused-argument + def _wrap_connect_internal(func, module, args, kwargs): + with tracer.start_as_current_span( + "connect", kind=trace.SpanKind.CLIENT + ): + return func(*args, **kwargs) + + return _wrap_connect_internal + + class EngineTracer: def __init__(self, tracer, engine, enable_commenter=False): self.tracer = tracer @@ -124,21 +140,15 @@ def _before_cur_exec( span.set_attribute(SpanAttributes.DB_SYSTEM, self.vendor) for key, value in attrs.items(): span.set_attribute(key, value) + if self.enable_commenter: + commenter_data = {} + commenter_data.update(_get_opentelemetry_values()) + statement = _add_sql_comment(statement, **commenter_data) context._otel_span = span - if self.enable_commenter: - statement = statement + EngineTracer._generate_comment(span=span) return statement, params - @staticmethod - def _generate_comment(span: Span) -> str: - span_context = span.get_span_context() - meta = {} - if span_context.is_valid: - meta.update(_generate_opentelemetry_traceparent(span)) - return _generate_sql_comment(**meta) - # pylint: disable=unused-argument def _after_cur_exec(conn, cursor, statement, params, context, executemany): diff --git a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/version.py b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/version.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py b/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py index 3b3c6d735b..5fa74376f9 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. import asyncio -import logging from unittest import mock import pytest @@ -21,7 +20,6 @@ from opentelemetry import trace from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor -from opentelemetry.instrumentation.sqlalchemy.engine import EngineTracer from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import TracerProvider, export from opentelemetry.test.test_base import TestBase @@ -46,9 +44,13 @@ def test_trace_integration(self): cnx.execute("SELECT 1 + 1;").fetchall() spans = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans), 1) - self.assertEqual(spans[0].name, "SELECT :memory:") + self.assertEqual(len(spans), 2) + # first span - the connection to the db + self.assertEqual(spans[0].name, "connect") self.assertEqual(spans[0].kind, trace.SpanKind.CLIENT) + # second span - the query itself + self.assertEqual(spans[1].name, "SELECT :memory:") + self.assertEqual(spans[1].kind, trace.SpanKind.CLIENT) def test_instrument_two_engines(self): engine_1 = create_engine("sqlite:///:memory:") @@ -65,8 +67,20 @@ def test_instrument_two_engines(self): cnx_2.execute("SELECT 1 + 1;").fetchall() spans = self.memory_exporter.get_finished_spans() + # 2 queries + 2 engine connect + self.assertEqual(len(spans), 4) - self.assertEqual(len(spans), 2) + def test_instrument_engine_connect(self): + engine = create_engine("sqlite:///:memory:") + + SQLAlchemyInstrumentor().instrument( + engine=engine, + tracer_provider=self.tracer_provider, + ) + + engine.connect() + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) @pytest.mark.skipif( not sqlalchemy.__version__.startswith("1.4"), @@ -85,11 +99,15 @@ async def run(): async with engine.connect() as cnx: await cnx.execute(sqlalchemy.text("SELECT 1 + 1;")) spans = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans), 1) - self.assertEqual(spans[0].name, "SELECT :memory:") + self.assertEqual(len(spans), 2) + # first span - the connection to the db + self.assertEqual(spans[0].name, "connect") self.assertEqual(spans[0].kind, trace.SpanKind.CLIENT) + # second span - the query + self.assertEqual(spans[1].name, "SELECT :memory:") + self.assertEqual(spans[1].kind, trace.SpanKind.CLIENT) self.assertEqual( - spans[0].instrumentation_scope.name, + spans[1].instrumentation_scope.name, "opentelemetry.instrumentation.sqlalchemy", ) @@ -99,7 +117,10 @@ def test_not_recording(self): mock_tracer = mock.Mock() mock_span = mock.Mock() mock_span.is_recording.return_value = False + mock_span.__enter__ = mock.Mock(return_value=(mock.Mock(), None)) + mock_span.__exit__ = mock.Mock(return_value=None) mock_tracer.start_span.return_value = mock_span + mock_tracer.start_as_current_span.return_value = mock_span with mock.patch("opentelemetry.trace.get_tracer") as tracer: tracer.return_value = mock_tracer engine = create_engine("sqlite:///:memory:") @@ -123,11 +144,15 @@ def test_create_engine_wrapper(self): cnx.execute("SELECT 1 + 1;").fetchall() spans = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans), 1) - self.assertEqual(spans[0].name, "SELECT :memory:") + self.assertEqual(len(spans), 2) + # first span - the connection to the db + self.assertEqual(spans[0].name, "connect") self.assertEqual(spans[0].kind, trace.SpanKind.CLIENT) + # second span - the query + self.assertEqual(spans[1].name, "SELECT :memory:") + self.assertEqual(spans[1].kind, trace.SpanKind.CLIENT) self.assertEqual( - spans[0].instrumentation_scope.name, + spans[1].instrumentation_scope.name, "opentelemetry.instrumentation.sqlalchemy", ) @@ -153,7 +178,7 @@ def test_custom_tracer_provider(self): cnx.execute("SELECT 1 + 1;").fetchall() spans = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans), 1) + self.assertEqual(len(spans), 2) self.assertEqual(spans[0].resource.attributes["service.name"], "test") self.assertEqual( spans[0].resource.attributes["deployment.environment"], "env" @@ -177,31 +202,16 @@ async def run(): async with engine.connect() as cnx: await cnx.execute(sqlalchemy.text("SELECT 1 + 1;")) spans = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans), 1) - self.assertEqual(spans[0].name, "SELECT :memory:") + self.assertEqual(len(spans), 2) + # first span - the connection to the db + self.assertEqual(spans[0].name, "connect") self.assertEqual(spans[0].kind, trace.SpanKind.CLIENT) + # second span - the query + self.assertEqual(spans[1].name, "SELECT :memory:") + self.assertEqual(spans[1].kind, trace.SpanKind.CLIENT) self.assertEqual( - spans[0].instrumentation_scope.name, + spans[1].instrumentation_scope.name, "opentelemetry.instrumentation.sqlalchemy", ) asyncio.get_event_loop().run_until_complete(run()) - - def test_generate_commenter(self): - logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO) - engine = create_engine("sqlite:///:memory:") - SQLAlchemyInstrumentor().instrument( - engine=engine, - tracer_provider=self.tracer_provider, - enable_commenter=True, - ) - - cnx = engine.connect() - cnx.execute("SELECT 1 + 1;").fetchall() - spans = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans), 1) - span = spans[0] - self.assertIn( - EngineTracer._generate_comment(span), - self.caplog.records[-2].getMessage(), - ) diff --git a/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlcommenter.py b/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlcommenter.py index 89f8d4cca7..8245d990f7 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlcommenter.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlcommenter.py @@ -11,6 +11,7 @@ # 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. +import logging import pytest from sqlalchemy import create_engine @@ -28,6 +29,17 @@ def tearDown(self): super().tearDown() SQLAlchemyInstrumentor().uninstrument() + def test_sqlcommenter_disabled(self): + logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO) + engine = create_engine("sqlite:///:memory:", echo=True) + SQLAlchemyInstrumentor().instrument( + engine=engine, tracer_provider=self.tracer_provider + ) + cnx = engine.connect() + cnx.execute("SELECT 1;").fetchall() + + self.assertEqual(self.caplog.records[-2].getMessage(), "SELECT 1;") + def test_sqlcommenter_enabled(self): engine = create_engine("sqlite:///:memory:") SQLAlchemyInstrumentor().instrument( @@ -39,15 +51,5 @@ def test_sqlcommenter_enabled(self): cnx.execute("SELECT 1;").fetchall() self.assertRegex( self.caplog.records[-2].getMessage(), - r"SELECT 1; /\*traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/", + r"SELECT 1 /\*traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;", ) - - def test_sqlcommenter_disabled(self): - engine = create_engine("sqlite:///:memory:", echo=True) - SQLAlchemyInstrumentor().instrument( - engine=engine, tracer_provider=self.tracer_provider - ) - cnx = engine.connect() - cnx.execute("SELECT 1;").fetchall() - - self.assertEqual(self.caplog.records[-2].getMessage(), "SELECT 1;") diff --git a/instrumentation/opentelemetry-instrumentation-sqlite3/setup.cfg b/instrumentation/opentelemetry-instrumentation-sqlite3/setup.cfg index cf7ed0ac86..14ce6ce7d8 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlite3/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-sqlite3/setup.cfg @@ -41,12 +41,12 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-instrumentation-dbapi == 0.31b0 - opentelemetry-instrumentation == 0.31b0 + opentelemetry-instrumentation-dbapi == 0.32b0 + opentelemetry-instrumentation == 0.32b0 [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 [options.packages.find] where = src diff --git a/instrumentation/opentelemetry-instrumentation-sqlite3/src/opentelemetry/instrumentation/sqlite3/version.py b/instrumentation/opentelemetry-instrumentation-sqlite3/src/opentelemetry/instrumentation/sqlite3/version.py index ca9e66477d..d0074fee82 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlite3/src/opentelemetry/instrumentation/sqlite3/version.py +++ b/instrumentation/opentelemetry-instrumentation-sqlite3/src/opentelemetry/instrumentation/sqlite3/version.py @@ -12,6 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" _instruments = tuple() diff --git a/instrumentation/opentelemetry-instrumentation-sqlite3/tests/test_sqlite3.py b/instrumentation/opentelemetry-instrumentation-sqlite3/tests/test_sqlite3.py index 714ca0ea02..581920232b 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlite3/tests/test_sqlite3.py +++ b/instrumentation/opentelemetry-instrumentation-sqlite3/tests/test_sqlite3.py @@ -21,30 +21,26 @@ class TestSQLite3(TestBase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls._connection = None - cls._cursor = None - cls._connection2 = None - cls._cursor2 = None - cls._tracer = cls.tracer_provider.get_tracer(__name__) - SQLite3Instrumentor().instrument(tracer_provider=cls.tracer_provider) - cls._connection = sqlite3.connect(":memory:") - cls._cursor = cls._connection.cursor() - cls._connection2 = dbapi2.connect(":memory:") - cls._cursor2 = cls._connection2.cursor() + def setUp(self): + super().setUp() + SQLite3Instrumentor().instrument(tracer_provider=self.tracer_provider) + self._tracer = self.tracer_provider.get_tracer(__name__) + self._connection = sqlite3.connect(":memory:") + self._cursor = self._connection.cursor() + self._connection2 = dbapi2.connect(":memory:") + self._cursor2 = self._connection2.cursor() - @classmethod - def tearDownClass(cls): - if cls._cursor: - cls._cursor.close() - if cls._connection: - cls._connection.close() - if cls._cursor2: - cls._cursor2.close() - if cls._connection2: - cls._connection2.close() + def tearDown(self): + super().tearDown() + if self._cursor: + self._cursor.close() + if self._connection: + self._connection.close() + if self._cursor2: + self._cursor2.close() + if self._connection2: + self._connection2.close() + SQLite3Instrumentor().uninstrument() def validate_spans(self, span_name): spans = self.memory_exporter.get_finished_spans() @@ -65,6 +61,12 @@ def validate_spans(self, span_name): self.assertIs(child_span.parent, root_span.get_span_context()) self.assertIs(child_span.kind, trace_api.SpanKind.CLIENT) + def _create_tables(self): + stmt = "CREATE TABLE IF NOT EXISTS test (id integer)" + self._cursor.execute(stmt) + self._cursor2.execute(stmt) + self.memory_exporter.clear() + def test_execute(self): """Should create a child span for execute method""" stmt = "CREATE TABLE IF NOT EXISTS test (id integer)" @@ -78,6 +80,9 @@ def test_execute(self): def test_executemany(self): """Should create a child span for executemany""" + self._create_tables() + + # real spans for executemany stmt = "INSERT INTO test (id) VALUES (?)" data = [("1",), ("2",), ("3",)] with self._tracer.start_as_current_span("rootSpan"): diff --git a/instrumentation/opentelemetry-instrumentation-starlette/setup.cfg b/instrumentation/opentelemetry-instrumentation-starlette/setup.cfg index 594009f1bf..4d20a8b2a0 100644 --- a/instrumentation/opentelemetry-instrumentation-starlette/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-starlette/setup.cfg @@ -41,10 +41,10 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-semantic-conventions == 0.31b0 - opentelemetry-instrumentation == 0.31b0 - opentelemetry-instrumentation-asgi == 0.31b0 - opentelemetry-util-http == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 + opentelemetry-instrumentation == 0.32b0 + opentelemetry-instrumentation-asgi == 0.32b0 + opentelemetry-util-http == 0.32b0 [options.entry_points] opentelemetry_instrumentor = @@ -52,7 +52,7 @@ opentelemetry_instrumentor = [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 requests ~= 2.23.0 # needed for testclient [options.packages.find] diff --git a/instrumentation/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/version.py b/instrumentation/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/version.py +++ b/instrumentation/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-system-metrics/setup.cfg b/instrumentation/opentelemetry-instrumentation-system-metrics/setup.cfg index b684abd00e..dc62f6a546 100644 --- a/instrumentation/opentelemetry-instrumentation-system-metrics/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-system-metrics/setup.cfg @@ -46,7 +46,7 @@ install_requires = [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 [options.packages.find] where = src diff --git a/instrumentation/opentelemetry-instrumentation-system-metrics/src/opentelemetry/instrumentation/system_metrics/version.py b/instrumentation/opentelemetry-instrumentation-system-metrics/src/opentelemetry/instrumentation/system_metrics/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-system-metrics/src/opentelemetry/instrumentation/system_metrics/version.py +++ b/instrumentation/opentelemetry-instrumentation-system-metrics/src/opentelemetry/instrumentation/system_metrics/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-tornado/setup.cfg b/instrumentation/opentelemetry-instrumentation-tornado/setup.cfg index fa0bcdf761..389f65c192 100644 --- a/instrumentation/opentelemetry-instrumentation-tornado/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-tornado/setup.cfg @@ -39,14 +39,14 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-instrumentation == 0.31b0 + opentelemetry-instrumentation == 0.32b0 opentelemetry-api ~= 1.3 - opentelemetry-semantic-conventions == 0.31b0 - opentelemetry-util-http == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 + opentelemetry-util-http == 0.32b0 [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 [options.entry_points] opentelemetry_instrumentor = diff --git a/instrumentation/opentelemetry-instrumentation-tornado/src/opentelemetry/instrumentation/tornado/version.py b/instrumentation/opentelemetry-instrumentation-tornado/src/opentelemetry/instrumentation/tornado/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-tornado/src/opentelemetry/instrumentation/tornado/version.py +++ b/instrumentation/opentelemetry-instrumentation-tornado/src/opentelemetry/instrumentation/tornado/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-urllib/setup.cfg b/instrumentation/opentelemetry-instrumentation-urllib/setup.cfg index 30ead6fc8e..cc77e7f387 100644 --- a/instrumentation/opentelemetry-instrumentation-urllib/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-urllib/setup.cfg @@ -41,13 +41,13 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-semantic-conventions == 0.31b0 - opentelemetry-instrumentation == 0.31b0 - opentelemetry-util-http == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 + opentelemetry-instrumentation == 0.32b0 + opentelemetry-util-http == 0.32b0 [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 httpretty ~= 1.0 [options.packages.find] diff --git a/instrumentation/opentelemetry-instrumentation-urllib/src/opentelemetry/instrumentation/urllib/__init__.py b/instrumentation/opentelemetry-instrumentation-urllib/src/opentelemetry/instrumentation/urllib/__init__.py index 3e38999ace..04244989f3 100644 --- a/instrumentation/opentelemetry-instrumentation-urllib/src/opentelemetry/instrumentation/urllib/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-urllib/src/opentelemetry/instrumentation/urllib/__init__.py @@ -75,6 +75,9 @@ def response_hook(span, request_obj, response) ) from opentelemetry import context + +# FIXME: fix the importing of this private attribute when the location of the _SUPPRESS_HTTP_INSTRUMENTATION_KEY is defined. +from opentelemetry.context import _SUPPRESS_HTTP_INSTRUMENTATION_KEY from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.instrumentation.urllib.package import _instruments from opentelemetry.instrumentation.urllib.version import __version__ @@ -88,12 +91,6 @@ def response_hook(span, request_obj, response) from opentelemetry.trace.status import Status from opentelemetry.util.http import remove_url_credentials -# A key to a context variable to avoid creating duplicate spans when instrumenting -# both, Session.request and Session.send, since Session.request calls into Session.send -_SUPPRESS_HTTP_INSTRUMENTATION_KEY = context.create_key( - "suppress_http_instrumentation" -) - _RequestHookT = typing.Optional[typing.Callable[[Span, Request], None]] _ResponseHookT = typing.Optional[ typing.Callable[[Span, Request, client.HTTPResponse], None] diff --git a/instrumentation/opentelemetry-instrumentation-urllib/src/opentelemetry/instrumentation/urllib/version.py b/instrumentation/opentelemetry-instrumentation-urllib/src/opentelemetry/instrumentation/urllib/version.py index ca9e66477d..d0074fee82 100644 --- a/instrumentation/opentelemetry-instrumentation-urllib/src/opentelemetry/instrumentation/urllib/version.py +++ b/instrumentation/opentelemetry-instrumentation-urllib/src/opentelemetry/instrumentation/urllib/version.py @@ -12,6 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" _instruments = tuple() diff --git a/instrumentation/opentelemetry-instrumentation-urllib/tests/test_urllib_integration.py b/instrumentation/opentelemetry-instrumentation-urllib/tests/test_urllib_integration.py index 405ff43c1e..d819d481cc 100644 --- a/instrumentation/opentelemetry-instrumentation-urllib/tests/test_urllib_integration.py +++ b/instrumentation/opentelemetry-instrumentation-urllib/tests/test_urllib_integration.py @@ -24,6 +24,9 @@ import opentelemetry.instrumentation.urllib # pylint: disable=no-name-in-module,import-error from opentelemetry import context, trace + +# FIXME: fix the importing of this private attribute when the location of the _SUPPRESS_HTTP_INSTRUMENTATION_KEY is defined. +from opentelemetry.context import _SUPPRESS_HTTP_INSTRUMENTATION_KEY from opentelemetry.instrumentation.urllib import ( # pylint: disable=no-name-in-module,import-error URLLibInstrumentor, ) @@ -188,6 +191,18 @@ def test_suppress_instrumentation(self): self.assert_span(num_spans=0) + def test_suppress_http_instrumentation(self): + token = context.attach( + context.set_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY, True) + ) + try: + result = self.perform_request(self.URL) + self.assertEqual(result.read(), b"Hello!") + finally: + context.detach(token) + + self.assert_span(num_spans=0) + def test_not_recording(self): with mock.patch("opentelemetry.trace.INVALID_SPAN") as mock_span: URLLibInstrumentor().uninstrument() diff --git a/instrumentation/opentelemetry-instrumentation-urllib3/setup.cfg b/instrumentation/opentelemetry-instrumentation-urllib3/setup.cfg index 9c717a4c42..0cb34fead1 100644 --- a/instrumentation/opentelemetry-instrumentation-urllib3/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-urllib3/setup.cfg @@ -41,14 +41,14 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-semantic-conventions == 0.31b0 - opentelemetry-instrumentation == 0.31b0 - opentelemetry-util-http == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 + opentelemetry-instrumentation == 0.32b0 + opentelemetry-util-http == 0.32b0 wrapt >= 1.0.0, < 2.0.0 [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 httpretty ~= 1.0 [options.packages.find] diff --git a/instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/__init__.py b/instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/__init__.py index ef04b7d0c2..e4973613d1 100644 --- a/instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/__init__.py @@ -72,6 +72,9 @@ def response_hook(span, request, response): import wrapt from opentelemetry import context + +# FIXME: fix the importing of this private attribute when the location of the _SUPPRESS_HTTP_INSTRUMENTATION_KEY is defined. +from opentelemetry.context import _SUPPRESS_HTTP_INSTRUMENTATION_KEY from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.instrumentation.urllib3.package import _instruments from opentelemetry.instrumentation.urllib3.version import __version__ @@ -86,12 +89,6 @@ def response_hook(span, request, response): from opentelemetry.trace.status import Status from opentelemetry.util.http.httplib import set_ip_on_next_http_connection -# A key to a context variable to avoid creating duplicate spans when instrumenting -# both, Session.request and Session.send, since Session.request calls into Session.send -_SUPPRESS_HTTP_INSTRUMENTATION_KEY = context.create_key( - "suppress_http_instrumentation" -) - _UrlFilterT = typing.Optional[typing.Callable[[str], str]] _RequestHookT = typing.Optional[ typing.Callable[ diff --git a/instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/version.py b/instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/version.py +++ b/instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_integration.py b/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_integration.py index a79ee8f941..2e70a9d2ab 100644 --- a/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_integration.py +++ b/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_integration.py @@ -20,10 +20,10 @@ import urllib3.exceptions from opentelemetry import context, trace -from opentelemetry.instrumentation.urllib3 import ( - _SUPPRESS_HTTP_INSTRUMENTATION_KEY, - URLLib3Instrumentor, -) + +# FIXME: fix the importing of this private attribute when the location of the _SUPPRESS_HTTP_INSTRUMENTATION_KEY is defined. +from opentelemetry.context import _SUPPRESS_HTTP_INSTRUMENTATION_KEY +from opentelemetry.instrumentation.urllib3 import URLLib3Instrumentor from opentelemetry.instrumentation.utils import _SUPPRESS_INSTRUMENTATION_KEY from opentelemetry.propagate import get_global_textmap, set_global_textmap from opentelemetry.semconv.trace import SpanAttributes diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg b/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg index 98e05e1f07..48f434aaed 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg @@ -41,13 +41,13 @@ package_dir= packages=find_namespace: install_requires = opentelemetry-api ~= 1.3 - opentelemetry-semantic-conventions == 0.31b0 - opentelemetry-instrumentation == 0.31b0 - opentelemetry-util-http == 0.31b0 + opentelemetry-semantic-conventions == 0.32b0 + opentelemetry-instrumentation == 0.32b0 + opentelemetry-util-http == 0.32b0 [options.extras_require] test = - opentelemetry-test-utils == 0.31b0 + opentelemetry-test-utils == 0.32b0 [options.packages.find] where = src diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py index 4f64217893..11c5acf643 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py @@ -158,6 +158,7 @@ def response_hook(span: Span, environ: WSGIEnvironment, status: str, response_he 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 @@ def response_hook(span: Span, environ: WSGIEnvironment, status: str, response_he 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 @@ def response_hook(span: Span, environ: WSGIEnvironment, status: str, response_he _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 @@ def _start_response(status, response_headers, *args, **kwargs): return _start_response + # pylint: disable=too-many-branches def __call__(self, environ, start_response): """The WSGI application @@ -383,13 +435,24 @@ def __call__(self, environ, start_response): 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 @@ def __call__(self, environ, start_response): 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 @@ def __call__(self, environ, start_response): 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 diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/package.py b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/package.py index 7a66a17a93..942f175da1 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/package.py +++ b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/package.py @@ -14,3 +14,5 @@ _instruments = tuple() + +_supports_metrics = True diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/version.py b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/version.py index d8dc1e1ed7..268a795344 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/version.py +++ b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/tests/test_wsgi_middleware.py b/instrumentation/opentelemetry-instrumentation-wsgi/tests/test_wsgi_middleware.py index 3238930792..eeaad4c3cb 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/tests/test_wsgi_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-wsgi/tests/test_wsgi_middleware.py @@ -20,6 +20,10 @@ 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 @@ def test_wsgi_internal_error(self): 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,11 +505,10 @@ def test_mark_span_internal_in_presence_of_span_from_other_framework(self): ) -class TestAdditionOfCustomRequestResponseHeaders(WsgiTestBase, TestBase): +class TestAdditionOfCustomRequestResponseHeaders(WsgiTestBase): def setUp(self): super().setUp() - tracer_provider, _ = TestBase.create_tracer_provider() - self.tracer = tracer_provider.get_tracer(__name__) + self.tracer = self.tracer_provider.get_tracer(__name__) def iterate_response(self, response): while True: diff --git a/opentelemetry-contrib-instrumentations/setup.cfg b/opentelemetry-contrib-instrumentations/setup.cfg index fa5aa4c853..89d2ea75db 100644 --- a/opentelemetry-contrib-instrumentations/setup.cfg +++ b/opentelemetry-contrib-instrumentations/setup.cfg @@ -28,46 +28,47 @@ packages = find_namespace: zip_safe = False include_package_data = True install_requires = - opentelemetry-instrumentation-aiohttp-client==0.31b0 - opentelemetry-instrumentation-aiopg==0.31b0 - opentelemetry-instrumentation-asgi==0.31b0 - opentelemetry-instrumentation-asyncpg==0.31b0 - opentelemetry-instrumentation-aws-lambda==0.31b0 - opentelemetry-instrumentation-boto==0.31b0 - opentelemetry-instrumentation-boto3sqs==0.31b0 - opentelemetry-instrumentation-botocore==0.31b0 - opentelemetry-instrumentation-celery==0.31b0 - opentelemetry-instrumentation-confluent-kafka==0.31b0 - opentelemetry-instrumentation-dbapi==0.31b0 - opentelemetry-instrumentation-django==0.31b0 - opentelemetry-instrumentation-elasticsearch==0.31b0 - opentelemetry-instrumentation-falcon==0.31b0 - opentelemetry-instrumentation-fastapi==0.31b0 - opentelemetry-instrumentation-flask==0.31b0 - opentelemetry-instrumentation-grpc==0.31b0 - opentelemetry-instrumentation-httpx==0.31b0 - opentelemetry-instrumentation-jinja2==0.31b0 - opentelemetry-instrumentation-kafka-python==0.31b0 - opentelemetry-instrumentation-logging==0.31b0 - opentelemetry-instrumentation-mysql==0.31b0 - opentelemetry-instrumentation-pika==0.31b0 - opentelemetry-instrumentation-psycopg2==0.31b0 - opentelemetry-instrumentation-pymemcache==0.31b0 - opentelemetry-instrumentation-pymongo==0.31b0 - opentelemetry-instrumentation-pymysql==0.31b0 - opentelemetry-instrumentation-pyramid==0.31b0 - opentelemetry-instrumentation-redis==0.31b0 - opentelemetry-instrumentation-remoulade==0.31b0 - opentelemetry-instrumentation-requests==0.31b0 - opentelemetry-instrumentation-sklearn==0.31b0 - opentelemetry-instrumentation-sqlalchemy==0.31b0 - opentelemetry-instrumentation-sqlite3==0.31b0 - opentelemetry-instrumentation-starlette==0.31b0 - opentelemetry-instrumentation-system-metrics==0.31b0 - opentelemetry-instrumentation-tornado==0.31b0 - opentelemetry-instrumentation-urllib==0.31b0 - opentelemetry-instrumentation-urllib3==0.31b0 - opentelemetry-instrumentation-wsgi==0.31b0 + opentelemetry-instrumentation-aio-pika==0.32b0 + opentelemetry-instrumentation-aiohttp-client==0.32b0 + opentelemetry-instrumentation-aiopg==0.32b0 + opentelemetry-instrumentation-asgi==0.32b0 + opentelemetry-instrumentation-asyncpg==0.32b0 + opentelemetry-instrumentation-aws-lambda==0.32b0 + opentelemetry-instrumentation-boto==0.32b0 + opentelemetry-instrumentation-boto3sqs==0.32b0 + opentelemetry-instrumentation-botocore==0.32b0 + opentelemetry-instrumentation-celery==0.32b0 + opentelemetry-instrumentation-confluent-kafka==0.32b0 + opentelemetry-instrumentation-dbapi==0.32b0 + opentelemetry-instrumentation-django==0.32b0 + opentelemetry-instrumentation-elasticsearch==0.32b0 + opentelemetry-instrumentation-falcon==0.32b0 + opentelemetry-instrumentation-fastapi==0.32b0 + opentelemetry-instrumentation-flask==0.32b0 + opentelemetry-instrumentation-grpc==0.32b0 + opentelemetry-instrumentation-httpx==0.32b0 + opentelemetry-instrumentation-jinja2==0.32b0 + opentelemetry-instrumentation-kafka-python==0.32b0 + opentelemetry-instrumentation-logging==0.32b0 + opentelemetry-instrumentation-mysql==0.32b0 + opentelemetry-instrumentation-pika==0.32b0 + opentelemetry-instrumentation-psycopg2==0.32b0 + opentelemetry-instrumentation-pymemcache==0.32b0 + opentelemetry-instrumentation-pymongo==0.32b0 + opentelemetry-instrumentation-pymysql==0.32b0 + opentelemetry-instrumentation-pyramid==0.32b0 + opentelemetry-instrumentation-redis==0.32b0 + opentelemetry-instrumentation-remoulade==0.32b0 + opentelemetry-instrumentation-requests==0.32b0 + opentelemetry-instrumentation-sklearn==0.32b0 + opentelemetry-instrumentation-sqlalchemy==0.32b0 + opentelemetry-instrumentation-sqlite3==0.32b0 + opentelemetry-instrumentation-starlette==0.32b0 + opentelemetry-instrumentation-system-metrics==0.32b0 + opentelemetry-instrumentation-tornado==0.32b0 + opentelemetry-instrumentation-urllib==0.32b0 + opentelemetry-instrumentation-urllib3==0.32b0 + opentelemetry-instrumentation-wsgi==0.32b0 [options.packages.find] where = src diff --git a/opentelemetry-contrib-instrumentations/src/opentelemetry/contrib-instrumentations/version.py b/opentelemetry-contrib-instrumentations/src/opentelemetry/contrib-instrumentations/version.py index d8dc1e1ed7..268a795344 100644 --- a/opentelemetry-contrib-instrumentations/src/opentelemetry/contrib-instrumentations/version.py +++ b/opentelemetry-contrib-instrumentations/src/opentelemetry/contrib-instrumentations/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/opentelemetry-distro/setup.cfg b/opentelemetry-distro/setup.cfg index 6abb95a37c..f13ef4d8a3 100644 --- a/opentelemetry-distro/setup.cfg +++ b/opentelemetry-distro/setup.cfg @@ -42,8 +42,8 @@ zip_safe = False include_package_data = True install_requires = opentelemetry-api ~= 1.3 - opentelemetry-instrumentation == 0.31b0 - opentelemetry-sdk == 1.12.0rc1 + opentelemetry-instrumentation == 0.32b0 + opentelemetry-sdk == 1.12.0rc2 [options.packages.find] where = src @@ -57,4 +57,4 @@ opentelemetry_configurator = [options.extras_require] test = otlp = - opentelemetry-exporter-otlp == 1.12.0rc1 + opentelemetry-exporter-otlp == 1.12.0rc2 diff --git a/opentelemetry-distro/src/opentelemetry/distro/version.py b/opentelemetry-distro/src/opentelemetry/distro/version.py index d8dc1e1ed7..268a795344 100644 --- a/opentelemetry-distro/src/opentelemetry/distro/version.py +++ b/opentelemetry-distro/src/opentelemetry/distro/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py index 6796b18c08..17cb83180c 100644 --- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py @@ -16,148 +16,152 @@ # RUN `python scripts/generate_instrumentation_bootstrap.py` TO REGENERATE. libraries = { + "aio_pika": { + "library": "aio_pika ~= 7.2.0", + "instrumentation": "opentelemetry-instrumentation-aio-pika==0.32b0", + }, "aiohttp": { "library": "aiohttp ~= 3.0", - "instrumentation": "opentelemetry-instrumentation-aiohttp-client==0.31b0", + "instrumentation": "opentelemetry-instrumentation-aiohttp-client==0.32b0", }, "aiopg": { "library": "aiopg >= 0.13.0, < 1.3.0", - "instrumentation": "opentelemetry-instrumentation-aiopg==0.31b0", + "instrumentation": "opentelemetry-instrumentation-aiopg==0.32b0", }, "asgiref": { "library": "asgiref ~= 3.0", - "instrumentation": "opentelemetry-instrumentation-asgi==0.31b0", + "instrumentation": "opentelemetry-instrumentation-asgi==0.32b0", }, "asyncpg": { "library": "asyncpg >= 0.12.0", - "instrumentation": "opentelemetry-instrumentation-asyncpg==0.31b0", + "instrumentation": "opentelemetry-instrumentation-asyncpg==0.32b0", }, "boto": { "library": "boto~=2.0", - "instrumentation": "opentelemetry-instrumentation-boto==0.31b0", + "instrumentation": "opentelemetry-instrumentation-boto==0.32b0", }, "boto3": { "library": "boto3 ~= 1.0", - "instrumentation": "opentelemetry-instrumentation-boto3sqs==0.31b0", + "instrumentation": "opentelemetry-instrumentation-boto3sqs==0.32b0", }, "botocore": { "library": "botocore ~= 1.0", - "instrumentation": "opentelemetry-instrumentation-botocore==0.31b0", + "instrumentation": "opentelemetry-instrumentation-botocore==0.32b0", }, "celery": { "library": "celery >= 4.0, < 6.0", - "instrumentation": "opentelemetry-instrumentation-celery==0.31b0", + "instrumentation": "opentelemetry-instrumentation-celery==0.32b0", }, "confluent-kafka": { "library": "confluent-kafka ~= 1.8.2", - "instrumentation": "opentelemetry-instrumentation-confluent-kafka==0.31b0", + "instrumentation": "opentelemetry-instrumentation-confluent-kafka==0.32b0", }, "django": { "library": "django >= 1.10", - "instrumentation": "opentelemetry-instrumentation-django==0.31b0", + "instrumentation": "opentelemetry-instrumentation-django==0.32b0", }, "elasticsearch": { "library": "elasticsearch >= 2.0", - "instrumentation": "opentelemetry-instrumentation-elasticsearch==0.31b0", + "instrumentation": "opentelemetry-instrumentation-elasticsearch==0.32b0", }, "falcon": { "library": "falcon >= 1.4.1, < 4.0.0", - "instrumentation": "opentelemetry-instrumentation-falcon==0.31b0", + "instrumentation": "opentelemetry-instrumentation-falcon==0.32b0", }, "fastapi": { "library": "fastapi ~= 0.58", - "instrumentation": "opentelemetry-instrumentation-fastapi==0.31b0", + "instrumentation": "opentelemetry-instrumentation-fastapi==0.32b0", }, "flask": { "library": "flask >= 1.0, < 3.0", - "instrumentation": "opentelemetry-instrumentation-flask==0.31b0", + "instrumentation": "opentelemetry-instrumentation-flask==0.32b0", }, "grpcio": { "library": "grpcio ~= 1.27", - "instrumentation": "opentelemetry-instrumentation-grpc==0.31b0", + "instrumentation": "opentelemetry-instrumentation-grpc==0.32b0", }, "httpx": { "library": "httpx >= 0.18.0", - "instrumentation": "opentelemetry-instrumentation-httpx==0.31b0", + "instrumentation": "opentelemetry-instrumentation-httpx==0.32b0", }, "jinja2": { "library": "jinja2 >= 2.7, < 4.0", - "instrumentation": "opentelemetry-instrumentation-jinja2==0.31b0", + "instrumentation": "opentelemetry-instrumentation-jinja2==0.32b0", }, "kafka-python": { "library": "kafka-python >= 2.0", - "instrumentation": "opentelemetry-instrumentation-kafka-python==0.31b0", + "instrumentation": "opentelemetry-instrumentation-kafka-python==0.32b0", }, "mysql-connector-python": { "library": "mysql-connector-python ~= 8.0", - "instrumentation": "opentelemetry-instrumentation-mysql==0.31b0", + "instrumentation": "opentelemetry-instrumentation-mysql==0.32b0", }, "pika": { "library": "pika >= 0.12.0", - "instrumentation": "opentelemetry-instrumentation-pika==0.31b0", + "instrumentation": "opentelemetry-instrumentation-pika==0.32b0", }, "psycopg2": { "library": "psycopg2 >= 2.7.3.1", - "instrumentation": "opentelemetry-instrumentation-psycopg2==0.31b0", + "instrumentation": "opentelemetry-instrumentation-psycopg2==0.32b0", }, "pymemcache": { "library": "pymemcache >= 1.3.5, < 4", - "instrumentation": "opentelemetry-instrumentation-pymemcache==0.31b0", + "instrumentation": "opentelemetry-instrumentation-pymemcache==0.32b0", }, "pymongo": { "library": "pymongo >= 3.1, < 5.0", - "instrumentation": "opentelemetry-instrumentation-pymongo==0.31b0", + "instrumentation": "opentelemetry-instrumentation-pymongo==0.32b0", }, "PyMySQL": { "library": "PyMySQL < 2", - "instrumentation": "opentelemetry-instrumentation-pymysql==0.31b0", + "instrumentation": "opentelemetry-instrumentation-pymysql==0.32b0", }, "pyramid": { "library": "pyramid >= 1.7", - "instrumentation": "opentelemetry-instrumentation-pyramid==0.31b0", + "instrumentation": "opentelemetry-instrumentation-pyramid==0.32b0", }, "redis": { "library": "redis >= 2.6", - "instrumentation": "opentelemetry-instrumentation-redis==0.31b0", + "instrumentation": "opentelemetry-instrumentation-redis==0.32b0", }, "remoulade": { "library": "remoulade >= 0.50", - "instrumentation": "opentelemetry-instrumentation-remoulade==0.31b0", + "instrumentation": "opentelemetry-instrumentation-remoulade==0.32b0", }, "requests": { "library": "requests ~= 2.0", - "instrumentation": "opentelemetry-instrumentation-requests==0.31b0", + "instrumentation": "opentelemetry-instrumentation-requests==0.32b0", }, "scikit-learn": { "library": "scikit-learn ~= 0.24.0", - "instrumentation": "opentelemetry-instrumentation-sklearn==0.31b0", + "instrumentation": "opentelemetry-instrumentation-sklearn==0.32b0", }, "sqlalchemy": { "library": "sqlalchemy", - "instrumentation": "opentelemetry-instrumentation-sqlalchemy==0.31b0", + "instrumentation": "opentelemetry-instrumentation-sqlalchemy==0.32b0", }, "starlette": { "library": "starlette ~= 0.13.0", - "instrumentation": "opentelemetry-instrumentation-starlette==0.31b0", + "instrumentation": "opentelemetry-instrumentation-starlette==0.32b0", }, "psutil": { "library": "psutil >= 5", - "instrumentation": "opentelemetry-instrumentation-system-metrics==0.31b0", + "instrumentation": "opentelemetry-instrumentation-system-metrics==0.32b0", }, "tornado": { "library": "tornado >= 5.1.1", - "instrumentation": "opentelemetry-instrumentation-tornado==0.31b0", + "instrumentation": "opentelemetry-instrumentation-tornado==0.32b0", }, "urllib3": { "library": "urllib3 >= 1.0.0, < 2.0.0", - "instrumentation": "opentelemetry-instrumentation-urllib3==0.31b0", + "instrumentation": "opentelemetry-instrumentation-urllib3==0.32b0", }, } default_instrumentations = [ - "opentelemetry-instrumentation-aws-lambda==0.31b0", - "opentelemetry-instrumentation-dbapi==0.31b0", - "opentelemetry-instrumentation-logging==0.31b0", - "opentelemetry-instrumentation-sqlite3==0.31b0", - "opentelemetry-instrumentation-urllib==0.31b0", - "opentelemetry-instrumentation-wsgi==0.31b0", + "opentelemetry-instrumentation-aws-lambda==0.32b0", + "opentelemetry-instrumentation-dbapi==0.32b0", + "opentelemetry-instrumentation-logging==0.32b0", + "opentelemetry-instrumentation-sqlite3==0.32b0", + "opentelemetry-instrumentation-urllib==0.32b0", + "opentelemetry-instrumentation-wsgi==0.32b0", ] diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/utils.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/utils.py index fea7608388..95a943afb3 100644 --- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/utils.py +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/utils.py @@ -24,7 +24,12 @@ # pylint: disable=E0611 from opentelemetry.context import _SUPPRESS_INSTRUMENTATION_KEY # noqa: F401 from opentelemetry.propagate import extract -from opentelemetry.trace import Span, StatusCode +from opentelemetry.trace import StatusCode +from opentelemetry.trace.propagation.tracecontext import ( + TraceContextTextMapPropagator, +) + +propagator = TraceContextTextMapPropagator() def extract_attributes_from_object( @@ -119,24 +124,22 @@ def _start_internal_or_server_span( return span, token -_KEY_VALUE_DELIMITER = "," - - -def _generate_sql_comment(**meta): +def _generate_sql_comment(**meta) -> str: """ Return a SQL comment with comma delimited key=value pairs created from **meta kwargs. """ + key_value_delimiter = "," + if not meta: # No entries added. return "" # Sort the keywords to ensure that caching works and that testing is # deterministic. It eases visual inspection as well. - # pylint: disable=consider-using-f-string return ( " /*" - + _KEY_VALUE_DELIMITER.join( - "{}={!r}".format(_url_quote(key), _url_quote(value)) + + key_value_delimiter.join( + f"{_url_quote(key)}={_url_quote(value)!r}" for key, value in sorted(meta.items()) if value is not None ) @@ -144,7 +147,7 @@ def _generate_sql_comment(**meta): ) -def _url_quote(s): # pylint: disable=invalid-name +def _url_quote(s) -> str: # pylint: disable=invalid-name if not isinstance(s, (str, bytes)): return s quoted = urllib.parse.quote(s) @@ -155,15 +158,15 @@ def _url_quote(s): # pylint: disable=invalid-name return quoted.replace("%", "%%") -def _generate_opentelemetry_traceparent(span: Span) -> str: - meta = {} - _version = "00" - _span_id = trace.format_span_id(span.context.span_id) - _trace_id = trace.format_trace_id(span.context.trace_id) - _flags = str(trace.TraceFlags.SAMPLED) - _traceparent = _version + "-" + _trace_id + "-" + _span_id + "-" + _flags - meta.update({"traceparent": _traceparent}) - return meta +def _get_opentelemetry_values() -> dict: + """ + Return the OpenTelemetry Trace and Span IDs if Span ID is set in the + OpenTelemetry execution context. + """ + # Insert the W3C TraceContext generated + _headers = {} + propagator.inject(_headers) + return _headers def _python_path_without_directory(python_path, directory, path_separator): @@ -172,3 +175,16 @@ def _python_path_without_directory(python_path, directory, path_separator): "", python_path, ) + + +def _add_sql_comment(sql, **meta) -> str: + """ + Appends comments to the sql statement and returns it + """ + comment = _generate_sql_comment(**meta) + sql = sql.rstrip() + if sql[-1] == ";": + sql = sql[:-1] + comment + ";" + else: + sql = sql + comment + return sql diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/version.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/version.py index d8dc1e1ed7..268a795344 100644 --- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/version.py +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/opentelemetry-instrumentation/tests/test_dependencies.py b/opentelemetry-instrumentation/tests/test_dependencies.py index a8acac62f4..04bcf476ea 100644 --- a/opentelemetry-instrumentation/tests/test_dependencies.py +++ b/opentelemetry-instrumentation/tests/test_dependencies.py @@ -26,9 +26,6 @@ class TestDependencyConflicts(TestBase): - def setUp(self): - pass - def test_get_dependency_conflicts_empty(self): self.assertIsNone(get_dependency_conflicts([])) diff --git a/opentelemetry-instrumentation/tests/test_propagators.py b/opentelemetry-instrumentation/tests/test_propagators.py index 62461aafa9..00ec65a128 100644 --- a/opentelemetry-instrumentation/tests/test_propagators.py +++ b/opentelemetry-instrumentation/tests/test_propagators.py @@ -14,6 +14,8 @@ # pylint: disable=protected-access +import unittest + from opentelemetry import trace from opentelemetry.instrumentation import propagators from opentelemetry.instrumentation.propagators import ( @@ -39,7 +41,7 @@ def test_get_set(self): propagators._RESPONSE_PROPAGATOR = original -class TestDictHeaderSetter(TestBase): +class TestDictHeaderSetter(unittest.TestCase): def test_simple(self): setter = DictHeaderSetter() carrier = {} diff --git a/opentelemetry-instrumentation/tests/test_utils.py b/opentelemetry-instrumentation/tests/test_utils.py index b7c9ecdc6e..3fd52ea7c6 100644 --- a/opentelemetry-instrumentation/tests/test_utils.py +++ b/opentelemetry-instrumentation/tests/test_utils.py @@ -12,17 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. +import unittest from http import HTTPStatus from opentelemetry.instrumentation.utils import ( + _add_sql_comment, _python_path_without_directory, http_status_to_status_code, ) -from opentelemetry.test.test_base import TestBase from opentelemetry.trace import StatusCode -class TestUtils(TestBase): +class TestUtils(unittest.TestCase): # See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#status def test_http_status_to_status_code(self): for status_code, expected in ( @@ -152,3 +153,36 @@ def test_remove_current_directory_from_python_path_linux_only_path(self): python_path, directory, path_separator ) self.assertEqual(actual_python_path, python_path) + + def test_add_sql_comments_with_semicolon(self): + sql_query_without_semicolon = "Select 1;" + comments = {"comment_1": "value 1", "comment 2": "value 3"} + commented_sql_without_semicolon = _add_sql_comment( + sql_query_without_semicolon, **comments + ) + + self.assertEqual( + commented_sql_without_semicolon, + "Select 1 /*comment%%202='value%%203',comment_1='value%%201'*/;", + ) + + def test_add_sql_comments_without_semicolon(self): + sql_query_without_semicolon = "Select 1" + comments = {"comment_1": "value 1", "comment 2": "value 3"} + commented_sql_without_semicolon = _add_sql_comment( + sql_query_without_semicolon, **comments + ) + + self.assertEqual( + commented_sql_without_semicolon, + "Select 1 /*comment%%202='value%%203',comment_1='value%%201'*/", + ) + + def test_add_sql_comments_without_comments(self): + sql_query_without_semicolon = "Select 1" + comments = {} + commented_sql_without_semicolon = _add_sql_comment( + sql_query_without_semicolon, **comments + ) + + self.assertEqual(commented_sql_without_semicolon, "Select 1") diff --git a/propagator/opentelemetry-propagator-ot-trace/src/opentelemetry/propagators/ot_trace/version.py b/propagator/opentelemetry-propagator-ot-trace/src/opentelemetry/propagators/ot_trace/version.py index d8dc1e1ed7..268a795344 100644 --- a/propagator/opentelemetry-propagator-ot-trace/src/opentelemetry/propagators/ot_trace/version.py +++ b/propagator/opentelemetry-propagator-ot-trace/src/opentelemetry/propagators/ot_trace/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/scripts/generate_instrumentation_readme.py b/scripts/generate_instrumentation_readme.py index dbaa42367c..00045cd73c 100755 --- a/scripts/generate_instrumentation_readme.py +++ b/scripts/generate_instrumentation_readme.py @@ -23,8 +23,8 @@ _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( diff --git a/tests/opentelemetry-docker-tests/tests/asyncpg/test_asyncpg_functional.py b/tests/opentelemetry-docker-tests/tests/asyncpg/test_asyncpg_functional.py index 9470f68b05..8111bce15e 100644 --- a/tests/opentelemetry-docker-tests/tests/asyncpg/test_asyncpg_functional.py +++ b/tests/opentelemetry-docker-tests/tests/asyncpg/test_asyncpg_functional.py @@ -21,14 +21,11 @@ def async_call(coro): class TestFunctionalAsyncPG(TestBase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls._connection = None - cls._cursor = None - cls._tracer = cls.tracer_provider.get_tracer(__name__) - AsyncPGInstrumentor().instrument(tracer_provider=cls.tracer_provider) - cls._connection = async_call( + def setUp(self): + super().setUp() + self._tracer = self.tracer_provider.get_tracer(__name__) + AsyncPGInstrumentor().instrument(tracer_provider=self.tracer_provider) + self._connection = async_call( asyncpg.connect( database=POSTGRES_DB_NAME, user=POSTGRES_USER, @@ -38,9 +35,9 @@ def setUpClass(cls): ) ) - @classmethod - def tearDownClass(cls): + def tearDown(self): AsyncPGInstrumentor().uninstrument() + super().tearDown() def check_span(self, span): self.assertEqual( @@ -148,16 +145,13 @@ def test_instrumented_method_doesnt_capture_parameters(self, *_, **__): class TestFunctionalAsyncPG_CaptureParameters(TestBase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls._connection = None - cls._cursor = None - cls._tracer = cls.tracer_provider.get_tracer(__name__) + def setUp(self): + super().setUp() + self._tracer = self.tracer_provider.get_tracer(__name__) AsyncPGInstrumentor(capture_parameters=True).instrument( - tracer_provider=cls.tracer_provider + tracer_provider=self.tracer_provider ) - cls._connection = async_call( + self._connection = async_call( asyncpg.connect( database=POSTGRES_DB_NAME, user=POSTGRES_USER, @@ -167,9 +161,9 @@ def setUpClass(cls): ) ) - @classmethod - def tearDownClass(cls): + def tearDown(self): AsyncPGInstrumentor().uninstrument() + super().tearDown() def check_span(self, span): self.assertEqual( diff --git a/tests/opentelemetry-docker-tests/tests/docker-compose.yml b/tests/opentelemetry-docker-tests/tests/docker-compose.yml index 8a33adb791..2f89e3388e 100644 --- a/tests/opentelemetry-docker-tests/tests/docker-compose.yml +++ b/tests/opentelemetry-docker-tests/tests/docker-compose.yml @@ -27,6 +27,17 @@ services: image: redis:4.0-alpine ports: - "127.0.0.1:6379:6379" + otrediscluster: + image: grokzen/redis-cluster:6.2.0 + environment: + - IP=0.0.0.0 + ports: + - "127.0.0.1:7000:7000" + - "127.0.0.1:7001:7001" + - "127.0.0.1:7002:7002" + - "127.0.0.1:7003:7003" + - "127.0.0.1:7004:7004" + - "127.0.0.1:7005:7005" otjaeger: image: jaegertracing/all-in-one:1.8 environment: diff --git a/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_functional.py b/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_functional.py index 59cf401e03..4f1305e866 100644 --- a/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_functional.py +++ b/tests/opentelemetry-docker-tests/tests/mysql/test_mysql_functional.py @@ -29,22 +29,10 @@ class TestFunctionalMysql(TestBase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls._connection = None - cls._cursor = None - cls._tracer = cls.tracer_provider.get_tracer(__name__) - MySQLInstrumentor().instrument() - - @classmethod - def tearDownClass(cls): - if cls._connection: - cls._connection.close() - MySQLInstrumentor().uninstrument() - def setUp(self): super().setUp() + self._tracer = self.tracer_provider.get_tracer(__name__) + MySQLInstrumentor().instrument() self._connection = mysql.connector.connect( user=MYSQL_USER, password=MYSQL_PASSWORD, @@ -54,6 +42,12 @@ def setUp(self): ) self._cursor = self._connection.cursor() + def tearDown(self): + self._cursor.close() + self._connection.close() + MySQLInstrumentor().uninstrument() + super().tearDown() + def validate_spans(self, span_name): spans = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans), 2) diff --git a/tests/opentelemetry-docker-tests/tests/postgres/test_aiopg_functional.py b/tests/opentelemetry-docker-tests/tests/postgres/test_aiopg_functional.py index 5f1d20bdc1..8157c06a32 100644 --- a/tests/opentelemetry-docker-tests/tests/postgres/test_aiopg_functional.py +++ b/tests/opentelemetry-docker-tests/tests/postgres/test_aiopg_functional.py @@ -36,14 +36,11 @@ def async_call(coro): class TestFunctionalAiopgConnect(TestBase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls._connection = None - cls._cursor = None - cls._tracer = cls.tracer_provider.get_tracer(__name__) - AiopgInstrumentor().instrument(tracer_provider=cls.tracer_provider) - cls._connection = async_call( + def setUp(self): + super().setUp() + self._tracer = self.tracer_provider.get_tracer(__name__) + AiopgInstrumentor().instrument(tracer_provider=self.tracer_provider) + self._connection = async_call( aiopg.connect( dbname=POSTGRES_DB_NAME, user=POSTGRES_USER, @@ -52,15 +49,13 @@ def setUpClass(cls): port=POSTGRES_PORT, ) ) - cls._cursor = async_call(cls._connection.cursor()) - - @classmethod - def tearDownClass(cls): - if cls._cursor: - cls._cursor.close() - if cls._connection: - cls._connection.close() + self._cursor = async_call(self._connection.cursor()) + + def tearDown(self): + self._cursor.close() + self._connection.close() AiopgInstrumentor().uninstrument() + super().tearDown() def validate_spans(self, span_name): spans = self.memory_exporter.get_finished_spans() @@ -121,18 +116,15 @@ def test_callproc(self): class TestFunctionalAiopgCreatePool(TestBase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls._connection = None - cls._cursor = None - cls._tracer = cls.tracer_provider.get_tracer(__name__) - AiopgInstrumentor().instrument(tracer_provider=cls.tracer_provider) - cls._dsn = ( + def setUp(self): + super().setUp() + self._tracer = self.tracer_provider.get_tracer(__name__) + AiopgInstrumentor().instrument(tracer_provider=self.tracer_provider) + self._dsn = ( f"dbname='{POSTGRES_DB_NAME}' user='{POSTGRES_USER}' password='{POSTGRES_PASSWORD}'" f" host='{POSTGRES_HOST}' port='{POSTGRES_PORT}'" ) - cls._pool = async_call( + self._pool = async_call( aiopg.create_pool( dbname=POSTGRES_DB_NAME, user=POSTGRES_USER, @@ -141,18 +133,15 @@ def setUpClass(cls): port=POSTGRES_PORT, ) ) - cls._connection = async_call(cls._pool.acquire()) - cls._cursor = async_call(cls._connection.cursor()) - - @classmethod - def tearDownClass(cls): - if cls._cursor: - cls._cursor.close() - if cls._connection: - cls._connection.close() - if cls._pool: - cls._pool.close() + self._connection = async_call(self._pool.acquire()) + self._cursor = async_call(self._connection.cursor()) + + def tearDown(self): + self._cursor.close() + self._connection.close() + self._pool.close() AiopgInstrumentor().uninstrument() + super().tearDown() def validate_spans(self, span_name): spans = self.memory_exporter.get_finished_spans() diff --git a/tests/opentelemetry-docker-tests/tests/postgres/test_psycopg_functional.py b/tests/opentelemetry-docker-tests/tests/postgres/test_psycopg_functional.py index 38ecff02e7..53112a5dbd 100644 --- a/tests/opentelemetry-docker-tests/tests/postgres/test_psycopg_functional.py +++ b/tests/opentelemetry-docker-tests/tests/postgres/test_psycopg_functional.py @@ -30,30 +30,25 @@ class TestFunctionalPsycopg(TestBase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls._connection = None - cls._cursor = None - cls._tracer = cls.tracer_provider.get_tracer(__name__) - Psycopg2Instrumentor().instrument(tracer_provider=cls.tracer_provider) - cls._connection = psycopg2.connect( + def setUp(self): + super().setUp() + self._tracer = self.tracer_provider.get_tracer(__name__) + Psycopg2Instrumentor().instrument(tracer_provider=self.tracer_provider) + self._connection = psycopg2.connect( dbname=POSTGRES_DB_NAME, user=POSTGRES_USER, password=POSTGRES_PASSWORD, host=POSTGRES_HOST, port=POSTGRES_PORT, ) - cls._connection.set_session(autocommit=True) - cls._cursor = cls._connection.cursor() - - @classmethod - def tearDownClass(cls): - if cls._cursor: - cls._cursor.close() - if cls._connection: - cls._connection.close() + self._connection.set_session(autocommit=True) + self._cursor = self._connection.cursor() + + def tearDown(self): + self._cursor.close() + self._connection.close() Psycopg2Instrumentor().uninstrument() + super().tearDown() def validate_spans(self, span_name): spans = self.memory_exporter.get_finished_spans() diff --git a/tests/opentelemetry-docker-tests/tests/postgres/test_psycopg_sqlcommenter.py b/tests/opentelemetry-docker-tests/tests/postgres/test_psycopg_sqlcommenter.py index 1e3460600e..4f5679a56b 100644 --- a/tests/opentelemetry-docker-tests/tests/postgres/test_psycopg_sqlcommenter.py +++ b/tests/opentelemetry-docker-tests/tests/postgres/test_psycopg_sqlcommenter.py @@ -26,34 +26,29 @@ class TestFunctionalPsycopg(TestBase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls._connection = None - cls._cursor = None - cls._tracer = cls.tracer_provider.get_tracer(__name__) + def setUp(self): + super().setUp() + self._tracer = self.tracer_provider.get_tracer(__name__) Psycopg2Instrumentor().instrument(enable_commenter=True) - cls._connection = psycopg2.connect( + self._connection = psycopg2.connect( dbname=POSTGRES_DB_NAME, user=POSTGRES_USER, password=POSTGRES_PASSWORD, host=POSTGRES_HOST, port=POSTGRES_PORT, ) - cls._connection.set_session(autocommit=True) - cls._cursor = cls._connection.cursor() + self._connection.set_session(autocommit=True) + self._cursor = self._connection.cursor() - @classmethod - def tearDownClass(cls): - if cls._cursor: - cls._cursor.close() - if cls._connection: - cls._connection.close() + def tearDown(self): + self._cursor.close() + self._connection.close() Psycopg2Instrumentor().uninstrument() + super().tearDown() def test_commenter_enabled(self): self._cursor.execute("SELECT 1;") self.assertRegex( self._cursor.query.decode("ascii"), - r"SELECT 1; /\*traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/", + r"SELECT 1 /\*traceparent='\d{1,2}-[a-zA-Z0-9_]{32}-[a-zA-Z0-9_]{16}-\d{1,2}'\*/;", ) diff --git a/tests/opentelemetry-docker-tests/tests/pymongo/test_pymongo_functional.py b/tests/opentelemetry-docker-tests/tests/pymongo/test_pymongo_functional.py index a716cd8def..92c694953b 100644 --- a/tests/opentelemetry-docker-tests/tests/pymongo/test_pymongo_functional.py +++ b/tests/opentelemetry-docker-tests/tests/pymongo/test_pymongo_functional.py @@ -28,16 +28,21 @@ class TestFunctionalPymongo(TestBase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls._tracer = cls.tracer_provider.get_tracer(__name__) - PymongoInstrumentor().instrument() + def setUp(self): + super().setUp() + self._tracer = self.tracer_provider.get_tracer(__name__) + self.instrumentor = PymongoInstrumentor() + self.instrumentor.instrument() + self.instrumentor._commandtracer_instance._tracer = self._tracer client = MongoClient( MONGODB_HOST, MONGODB_PORT, serverSelectionTimeoutMS=2000 ) db = client[MONGODB_DB_NAME] - cls._collection = db[MONGODB_COLLECTION_NAME] + self._collection = db[MONGODB_COLLECTION_NAME] + + def tearDown(self): + self.instrumentor.uninstrument() + super().tearDown() def validate_spans(self): spans = self.memory_exporter.get_finished_spans() diff --git a/tests/opentelemetry-docker-tests/tests/pymysql/test_pymysql_functional.py b/tests/opentelemetry-docker-tests/tests/pymysql/test_pymysql_functional.py index 881ef05d2a..83f7abf281 100644 --- a/tests/opentelemetry-docker-tests/tests/pymysql/test_pymysql_functional.py +++ b/tests/opentelemetry-docker-tests/tests/pymysql/test_pymysql_functional.py @@ -29,27 +29,24 @@ class TestFunctionalPyMysql(TestBase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls._connection = None - cls._cursor = None - cls._tracer = cls.tracer_provider.get_tracer(__name__) + def setUp(self): + super().setUp() + self._tracer = self.tracer_provider.get_tracer(__name__) PyMySQLInstrumentor().instrument() - cls._connection = pymy.connect( + self._connection = pymy.connect( user=MYSQL_USER, password=MYSQL_PASSWORD, host=MYSQL_HOST, port=MYSQL_PORT, database=MYSQL_DB_NAME, ) - cls._cursor = cls._connection.cursor() + self._cursor = self._connection.cursor() - @classmethod - def tearDownClass(cls): - if cls._connection: - cls._connection.close() + def tearDown(self): + self._cursor.close() + self._connection.close() PyMySQLInstrumentor().uninstrument() + super().tearDown() def validate_spans(self, span_name): spans = self.memory_exporter.get_finished_spans() diff --git a/tests/opentelemetry-docker-tests/tests/redis/test_redis_functional.py b/tests/opentelemetry-docker-tests/tests/redis/test_redis_functional.py index 0b5d49ca10..21d7e36b00 100644 --- a/tests/opentelemetry-docker-tests/tests/redis/test_redis_functional.py +++ b/tests/opentelemetry-docker-tests/tests/redis/test_redis_functional.py @@ -31,8 +31,8 @@ def setUp(self): RedisInstrumentor().instrument(tracer_provider=self.tracer_provider) def tearDown(self): - super().tearDown() RedisInstrumentor().uninstrument() + super().tearDown() def _check_span(self, span, name): self.assertEqual(span.name, name) @@ -124,6 +124,72 @@ def test_parent(self): self.assertEqual(child_span.name, "GET") +class TestRedisClusterInstrument(TestBase): + def setUp(self): + super().setUp() + self.redis_client = redis.cluster.RedisCluster( + host="localhost", port=7000 + ) + self.redis_client.flushall() + RedisInstrumentor().instrument(tracer_provider=self.tracer_provider) + + def tearDown(self): + super().tearDown() + RedisInstrumentor().uninstrument() + + def _check_span(self, span, name): + self.assertEqual(span.name, name) + self.assertIs(span.status.status_code, trace.StatusCode.UNSET) + + def test_basics(self): + self.assertIsNone(self.redis_client.get("cheese")) + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) + span = spans[0] + self._check_span(span, "GET") + self.assertEqual( + span.attributes.get(SpanAttributes.DB_STATEMENT), "GET cheese" + ) + self.assertEqual(span.attributes.get("db.redis.args_length"), 2) + + def test_pipeline_traced(self): + with self.redis_client.pipeline(transaction=False) as pipeline: + pipeline.set("blah", 32) + pipeline.rpush("foo", "éé") + pipeline.hgetall("xxx") + pipeline.execute() + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) + span = spans[0] + self._check_span(span, "SET RPUSH HGETALL") + self.assertEqual( + span.attributes.get(SpanAttributes.DB_STATEMENT), + "SET blah 32\nRPUSH foo éé\nHGETALL xxx", + ) + self.assertEqual(span.attributes.get("db.redis.pipeline_length"), 3) + + def test_parent(self): + """Ensure OpenTelemetry works with redis.""" + ot_tracer = trace.get_tracer("redis_svc") + + with ot_tracer.start_as_current_span("redis_get"): + self.assertIsNone(self.redis_client.get("cheese")) + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 2) + child_span, parent_span = spans[0], spans[1] + + # confirm the parenting + self.assertIsNone(parent_span.parent) + self.assertIs(child_span.parent, parent_span.get_span_context()) + + self.assertEqual(parent_span.name, "redis_get") + self.assertEqual(parent_span.instrumentation_info.name, "redis_svc") + + self.assertEqual(child_span.name, "GET") + + def async_call(coro): loop = asyncio.get_event_loop() return loop.run_until_complete(coro) @@ -137,8 +203,8 @@ def setUp(self): RedisInstrumentor().instrument(tracer_provider=self.tracer_provider) def tearDown(self): - super().tearDown() RedisInstrumentor().uninstrument() + super().tearDown() def _check_span(self, span, name): self.assertEqual(span.name, name) @@ -238,6 +304,77 @@ def test_parent(self): self.assertEqual(child_span.name, "GET") +class TestAsyncRedisClusterInstrument(TestBase): + def setUp(self): + super().setUp() + self.redis_client = redis.asyncio.cluster.RedisCluster( + host="localhost", port=7000 + ) + async_call(self.redis_client.flushall()) + RedisInstrumentor().instrument(tracer_provider=self.tracer_provider) + + def tearDown(self): + super().tearDown() + RedisInstrumentor().uninstrument() + + def _check_span(self, span, name): + self.assertEqual(span.name, name) + self.assertIs(span.status.status_code, trace.StatusCode.UNSET) + + def test_basics(self): + self.assertIsNone(async_call(self.redis_client.get("cheese"))) + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) + span = spans[0] + self._check_span(span, "GET") + self.assertEqual( + span.attributes.get(SpanAttributes.DB_STATEMENT), "GET cheese" + ) + self.assertEqual(span.attributes.get("db.redis.args_length"), 2) + + def test_pipeline_traced(self): + async def pipeline_simple(): + async with self.redis_client.pipeline( + transaction=False + ) as pipeline: + pipeline.set("blah", 32) + pipeline.rpush("foo", "éé") + pipeline.hgetall("xxx") + await pipeline.execute() + + async_call(pipeline_simple()) + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) + span = spans[0] + self._check_span(span, "SET RPUSH HGETALL") + self.assertEqual( + span.attributes.get(SpanAttributes.DB_STATEMENT), + "SET blah 32\nRPUSH foo éé\nHGETALL xxx", + ) + self.assertEqual(span.attributes.get("db.redis.pipeline_length"), 3) + + def test_parent(self): + """Ensure OpenTelemetry works with redis.""" + ot_tracer = trace.get_tracer("redis_svc") + + with ot_tracer.start_as_current_span("redis_get"): + self.assertIsNone(async_call(self.redis_client.get("cheese"))) + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 2) + child_span, parent_span = spans[0], spans[1] + + # confirm the parenting + self.assertIsNone(parent_span.parent) + self.assertIs(child_span.parent, parent_span.get_span_context()) + + self.assertEqual(parent_span.name, "redis_get") + self.assertEqual(parent_span.instrumentation_info.name, "redis_svc") + + self.assertEqual(child_span.name, "GET") + + class TestRedisDBIndexInstrument(TestBase): def setUp(self): super().setUp() @@ -246,8 +383,8 @@ def setUp(self): RedisInstrumentor().instrument(tracer_provider=self.tracer_provider) def tearDown(self): - super().tearDown() RedisInstrumentor().uninstrument() + super().tearDown() def _check_span(self, span, name): self.assertEqual(span.name, name) diff --git a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py index 084b8114a1..57317b76c6 100644 --- a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py +++ b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py @@ -128,8 +128,9 @@ def test_orm_insert(self): self.session.commit() spans = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans), 1) - span = spans[0] + # one span for the connection and one for the query + self.assertEqual(len(spans), 2) + span = spans[1] stmt = "INSERT INTO players (id, name) VALUES " if span.attributes.get(SpanAttributes.DB_SYSTEM) == "sqlite": stmt += "(?, ?)" @@ -148,8 +149,9 @@ def test_session_query(self): self.assertEqual(len(out), 0) spans = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans), 1) - span = spans[0] + # one span for the connection and one for the query + self.assertEqual(len(spans), 2) + span = spans[1] stmt = "SELECT players.id AS players_id, players.name AS players_name \nFROM players \nWHERE players.name = " if span.attributes.get(SpanAttributes.DB_SYSTEM) == "sqlite": stmt += "?" @@ -170,8 +172,9 @@ def test_engine_connect_execute(self): self.assertEqual(len(rows), 0) spans = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans), 1) - span = spans[0] + # one span for the connection and one for the query + self.assertEqual(len(spans), 2) + span = spans[1] self._check_span(span, "SELECT") self.assertEqual( span.attributes.get(SpanAttributes.DB_STATEMENT), @@ -190,8 +193,9 @@ def test_parent(self): self.assertEqual(len(rows), 0) spans = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans), 2) - child_span, parent_span = spans + # one span for the connection and two for the queries + self.assertEqual(len(spans), 3) + _, child_span, parent_span = spans # confirm the parenting self.assertIsNone(parent_span.parent) @@ -247,5 +251,5 @@ def insert_players(session): # batch inserts together which means `insert_players` only generates one span. # See https://docs.sqlalchemy.org/en/14/changelog/migration_14.html#orm-batch-inserts-with-psycopg2-now-batch-statements-with-returning-in-most-cases self.assertEqual( - len(spans), 5 if self.VENDOR not in ["postgresql"] else 3 + len(spans), 8 if self.VENDOR not in ["postgresql"] else 6 ) diff --git a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_instrument.py b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_instrument.py index 8c97d100e0..48944d58e0 100644 --- a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_instrument.py +++ b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_instrument.py @@ -35,6 +35,7 @@ class SQLAlchemyInstrumentTestCase(TestBase): """ def setUp(self): + super().setUp() # create a traced engine with the given arguments SQLAlchemyInstrumentor().instrument() dsn = ( @@ -45,23 +46,23 @@ def setUp(self): # prepare a connection self.conn = self.engine.connect() - super().setUp() def tearDown(self): # clear the database and dispose the engine self.conn.close() self.engine.dispose() SQLAlchemyInstrumentor().uninstrument() + super().tearDown() def test_engine_traced(self): # ensures that the engine is traced rows = self.conn.execute("SELECT").fetchall() self.assertEqual(len(rows), 1) - traces = self.memory_exporter.get_finished_spans() + spans = self.memory_exporter.get_finished_spans() # trace composition - self.assertEqual(len(traces), 1) - span = traces[0] + self.assertEqual(len(spans), 2) + span = spans[1] # check subset of span fields self.assertEqual(span.name, "SELECT opentelemetry-tests") self.assertIs(span.status.status_code, trace.StatusCode.UNSET) diff --git a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_mssql.py b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_mssql.py index e3a1aae2ef..fef1bf7b7b 100644 --- a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_mssql.py +++ b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_mssql.py @@ -69,8 +69,9 @@ def test_engine_execute_errors(self): conn.execute("SELECT * FROM a_wrong_table").fetchall() spans = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans), 1) - span = spans[0] + # one span for the connection and one for the query + self.assertEqual(len(spans), 2) + span = spans[1] # span fields self.assertEqual(span.name, "SELECT opentelemetry-tests") self.assertEqual( @@ -96,9 +97,9 @@ def test_orm_insert(self): self.session.commit() spans = self.memory_exporter.get_finished_spans() - # identity insert on before the insert, insert, and identity insert off after the insert - self.assertEqual(len(spans), 3) - span = spans[1] + # connect, identity insert on before the insert, insert, and identity insert off after the insert + self.assertEqual(len(spans), 4) + span = spans[2] self._check_span(span, "INSERT") self.assertIn( "INSERT INTO players", diff --git a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_mysql.py b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_mysql.py index 69b3c26286..ceebe78ef5 100644 --- a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_mysql.py +++ b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_mysql.py @@ -68,8 +68,9 @@ def test_engine_execute_errors(self): conn.execute("SELECT * FROM a_wrong_table").fetchall() spans = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans), 1) - span = spans[0] + # one span for the connection and one for the query + self.assertEqual(len(spans), 2) + span = spans[1] # span fields self.assertEqual(span.name, "SELECT opentelemetry-tests") self.assertEqual( diff --git a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_postgres.py b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_postgres.py index 2893a5fd19..bbc62bfbbf 100644 --- a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_postgres.py +++ b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_postgres.py @@ -62,8 +62,9 @@ def test_engine_execute_errors(self): conn.execute("SELECT * FROM a_wrong_table").fetchall() spans = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans), 1) - span = spans[0] + # one span for the connection and one for the query + self.assertEqual(len(spans), 2) + span = spans[1] # span fields self.assertEqual(span.name, "SELECT opentelemetry-tests") self.assertEqual( diff --git a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_sqlite.py b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_sqlite.py index 2197028266..884e3a37c2 100644 --- a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_sqlite.py +++ b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_sqlite.py @@ -38,8 +38,9 @@ def test_engine_execute_errors(self): conn.execute(stmt).fetchall() spans = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans), 1) - span = spans[0] + # one span for the connection and one span for the query + self.assertEqual(len(spans), 2) + span = spans[1] # span fields self.assertEqual(span.name, "SELECT :memory:") self.assertEqual( diff --git a/tox.ini b/tox.ini index 90988e207c..64210a8c0c 100644 --- a/tox.ini +++ b/tox.ini @@ -454,6 +454,7 @@ commands_pre = python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-sqlalchemy[test] python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-celery[test] python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-pika[test] + python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-aio-pika[test] python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-sklearn[test] python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-redis[test] python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-remoulade[test] @@ -500,7 +501,7 @@ deps = psycopg2 ~= 2.8.4 aiopg >= 0.13.0, < 1.3.0 sqlalchemy ~= 1.4 - redis ~= 4.2 + redis ~= 4.3 celery[pytest] >= 4.0, < 6.0 protobuf~=3.13 requests==2.25.0 diff --git a/util/opentelemetry-util-http/src/opentelemetry/util/http/version.py b/util/opentelemetry-util-http/src/opentelemetry/util/http/version.py index d8dc1e1ed7..268a795344 100644 --- a/util/opentelemetry-util-http/src/opentelemetry/util/http/version.py +++ b/util/opentelemetry-util-http/src/opentelemetry/util/http/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.31b0" +__version__ = "0.32b0" diff --git a/util/opentelemetry-util-http/tests/test_capture_custom_headers.py b/util/opentelemetry-util-http/tests/test_capture_custom_headers.py index eb1a4f6a7e..e6e1583ffb 100644 --- a/util/opentelemetry-util-http/tests/test_capture_custom_headers.py +++ b/util/opentelemetry-util-http/tests/test_capture_custom_headers.py @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +import unittest from unittest.mock import patch -from opentelemetry.test.test_base import TestBase from opentelemetry.util.http import ( OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST, OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_RESPONSE, @@ -24,7 +24,7 @@ ) -class TestCaptureCustomHeaders(TestBase): +class TestCaptureCustomHeaders(unittest.TestCase): @patch.dict( "os.environ", { diff --git a/util/opentelemetry-util-http/tests/test_http_excluded_urls.py b/util/opentelemetry-util-http/tests/test_http_excluded_urls.py index af524d2e7b..3fea381050 100644 --- a/util/opentelemetry-util-http/tests/test_http_excluded_urls.py +++ b/util/opentelemetry-util-http/tests/test_http_excluded_urls.py @@ -12,13 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +import unittest from unittest.mock import patch -from opentelemetry.test.test_base import TestBase from opentelemetry.util.http import get_excluded_urls -class TestGetExcludedUrls(TestBase): +class TestGetExcludedUrls(unittest.TestCase): @patch.dict( "os.environ", {