From 953a113ebf2a0e42dfe4da34037a1253ea0add8c Mon Sep 17 00:00:00 2001 From: Markus Jonsson Date: Fri, 1 Mar 2024 23:41:17 +0000 Subject: [PATCH] * refactor on package name psycopg insteead of psycopg3 --- .github/workflows/instrumentations_1.yml | 1 - docs-requirements.txt | 2 +- docs/instrumentation/psycopg3/psycopg3.rst | 7 - instrumentation/README.md | 1 - .../instrumentation/psycopg/__init__.py | 81 +++ .../tests/test_psycopg_integration.py | 220 ++++++++ .../LICENSE | 201 ------- .../README.rst | 21 - .../pyproject.toml | 58 --- .../instrumentation/psycopg3/__init__.py | 344 ------------ .../instrumentation/psycopg3/package.py | 16 - .../instrumentation/psycopg3/version.py | 15 - .../tests/__init__.py | 0 .../tests/test_psycopg3_integration.py | 491 ------------------ .../instrumentation/bootstrap_gen.py | 4 - tox.ini | 2 +- 16 files changed, 303 insertions(+), 1161 deletions(-) delete mode 100644 docs/instrumentation/psycopg3/psycopg3.rst delete mode 100644 instrumentation/opentelemetry-instrumentation-psycopg3/LICENSE delete mode 100644 instrumentation/opentelemetry-instrumentation-psycopg3/README.rst delete mode 100644 instrumentation/opentelemetry-instrumentation-psycopg3/pyproject.toml delete mode 100644 instrumentation/opentelemetry-instrumentation-psycopg3/src/opentelemetry/instrumentation/psycopg3/__init__.py delete mode 100644 instrumentation/opentelemetry-instrumentation-psycopg3/src/opentelemetry/instrumentation/psycopg3/package.py delete mode 100644 instrumentation/opentelemetry-instrumentation-psycopg3/src/opentelemetry/instrumentation/psycopg3/version.py delete mode 100644 instrumentation/opentelemetry-instrumentation-psycopg3/tests/__init__.py delete mode 100644 instrumentation/opentelemetry-instrumentation-psycopg3/tests/test_psycopg3_integration.py diff --git a/.github/workflows/instrumentations_1.yml b/.github/workflows/instrumentations_1.yml index 56c0fde8a1..0640c056a6 100644 --- a/.github/workflows/instrumentations_1.yml +++ b/.github/workflows/instrumentations_1.yml @@ -31,7 +31,6 @@ jobs: - "richconsole" - "psycopg" - "prometheus-remote-write" - - "psycopg3" - "sdkextension-aws" - "propagator-aws-xray" - "propagator-ot-trace" diff --git a/docs-requirements.txt b/docs-requirements.txt index 594b094cae..aff449fcf8 100644 --- a/docs-requirements.txt +++ b/docs-requirements.txt @@ -36,7 +36,7 @@ kafka-python>=2.0,<3.0 mysql-connector-python~=8.0 mysqlclient~=2.1.1 psutil>=5 -psycopg>=3.1.17 +psycopg~=3.1.17 pika>=0.12.0 pymongo~=3.1 PyMySQL~=0.9.3 diff --git a/docs/instrumentation/psycopg3/psycopg3.rst b/docs/instrumentation/psycopg3/psycopg3.rst deleted file mode 100644 index 86b0a766f8..0000000000 --- a/docs/instrumentation/psycopg3/psycopg3.rst +++ /dev/null @@ -1,7 +0,0 @@ -OpenTelemetry Psycopg3 Instrumentation -===================================== - -.. automodule:: opentelemetry.instrumentation.psycopg3 - :members: - :undoc-members: - :show-inheritance: diff --git a/instrumentation/README.md b/instrumentation/README.md index ab8f1a23ba..0cce7e5de7 100644 --- a/instrumentation/README.md +++ b/instrumentation/README.md @@ -31,7 +31,6 @@ | [opentelemetry-instrumentation-pika](./opentelemetry-instrumentation-pika) | pika >= 0.12.0 | No | [opentelemetry-instrumentation-psycopg](./opentelemetry-instrumentation-psycopg) | psycopg >= 3.1.0 | No | [opentelemetry-instrumentation-psycopg2](./opentelemetry-instrumentation-psycopg2) | psycopg2 >= 2.7.3.1 | No -| [opentelemetry-instrumentation-psycopg3](./opentelemetry-instrumentation-psycopg3) | psycopg >= 3.1.12 | No | [opentelemetry-instrumentation-pymemcache](./opentelemetry-instrumentation-pymemcache) | pymemcache >= 1.3.5, < 5 | No | [opentelemetry-instrumentation-pymongo](./opentelemetry-instrumentation-pymongo) | pymongo >= 3.1, < 5.0 | No | [opentelemetry-instrumentation-pymysql](./opentelemetry-instrumentation-pymysql) | PyMySQL < 2 | No diff --git a/instrumentation/opentelemetry-instrumentation-psycopg/src/opentelemetry/instrumentation/psycopg/__init__.py b/instrumentation/opentelemetry-instrumentation-psycopg/src/opentelemetry/instrumentation/psycopg/__init__.py index ab473c2fe4..377a590c52 100644 --- a/instrumentation/opentelemetry-instrumentation-psycopg/src/opentelemetry/instrumentation/psycopg/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-psycopg/src/opentelemetry/instrumentation/psycopg/__init__.py @@ -106,6 +106,7 @@ from typing import Collection import psycopg +from psycopg import AsyncCursor as pg_async_cursor from psycopg import Cursor as pg_cursor # pylint: disable=no-name-in-module from psycopg.sql import Composed # pylint: disable=no-name-in-module @@ -151,9 +152,36 @@ def _instrument(self, **kwargs): commenter_options=commenter_options, ) + dbapi.wrap_connect( + __name__, + psycopg.Connection, + "connect", + self._DATABASE_SYSTEM, + self._CONNECTION_ATTRIBUTES, + version=__version__, + tracer_provider=tracer_provider, + db_api_integration_factory=DatabaseApiIntegration, + enable_commenter=enable_sqlcommenter, + commenter_options=commenter_options, + ) + dbapi.wrap_connect( + __name__, + psycopg.AsyncConnection, + "connect", + self._DATABASE_SYSTEM, + self._CONNECTION_ATTRIBUTES, + version=__version__, + tracer_provider=tracer_provider, + db_api_integration_factory=DatabaseApiAsyncIntegration, + enable_commenter=enable_sqlcommenter, + commenter_options=commenter_options, + ) + def _uninstrument(self, **kwargs): """ "Disable Psycopg instrumentation""" dbapi.unwrap_connect(psycopg, "connect") + dbapi.unwrap_connect(psycopg.Connection, "connect") + dbapi.unwrap_connect(psycopg.AsyncConnection, "connect") # TODO(owais): check if core dbapi can do this for all dbapi implementations e.g, pymysql and mysql @staticmethod @@ -204,6 +232,26 @@ def wrapped_connection( return connection +class DatabaseApiAsyncIntegration(dbapi.DatabaseApiIntegration): + async def wrapped_connection( + self, + connect_method: typing.Callable[..., typing.Any], + args: typing.Tuple[typing.Any, typing.Any], + kwargs: typing.Dict[typing.Any, typing.Any], + ): + """Add object proxy to connection object.""" + base_cursor_factory = kwargs.pop("cursor_factory", None) + new_factory_kwargs = {"db_api": self} + if base_cursor_factory: + new_factory_kwargs["base_factory"] = base_cursor_factory + kwargs["cursor_factory"] = _new_cursor_async_factory( + **new_factory_kwargs + ) + connection = await connect_method(*args, **kwargs) + self.get_connection_attributes(connection) + return connection + + class CursorTracer(dbapi.CursorTracer): def get_operation_name(self, cursor, args): if not args: @@ -259,3 +307,36 @@ def callproc(self, *args, **kwargs): ) return TracedCursorFactory + + +def _new_cursor_async_factory( + db_api=None, base_factory=None, tracer_provider=None +): + if not db_api: + db_api = DatabaseApiAsyncIntegration( + __name__, + Psycopg3Instrumentor._DATABASE_SYSTEM, + connection_attributes=Psycopg3Instrumentor._CONNECTION_ATTRIBUTES, + version=__version__, + tracer_provider=tracer_provider, + ) + base_factory = base_factory or pg_async_cursor + _cursor_tracer = CursorTracer(db_api) + + class TracedCursorAsyncFactory(base_factory): + async def execute(self, *args, **kwargs): + return await _cursor_tracer.traced_execution( + self, super().execute, *args, **kwargs + ) + + async def executemany(self, *args, **kwargs): + return await _cursor_tracer.traced_execution( + self, super().executemany, *args, **kwargs + ) + + async def callproc(self, *args, **kwargs): + return await _cursor_tracer.traced_execution( + self, super().callproc, *args, **kwargs + ) + + return TracedCursorAsyncFactory diff --git a/instrumentation/opentelemetry-instrumentation-psycopg/tests/test_psycopg_integration.py b/instrumentation/opentelemetry-instrumentation-psycopg/tests/test_psycopg_integration.py index d5e4bc65f3..6b181bb9fb 100644 --- a/instrumentation/opentelemetry-instrumentation-psycopg/tests/test_psycopg_integration.py +++ b/instrumentation/opentelemetry-instrumentation-psycopg/tests/test_psycopg_integration.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import asyncio import types from unittest import mock @@ -23,6 +24,11 @@ from opentelemetry.test.test_base import TestBase +def async_call(coro, *args, **kwargs): + loop = asyncio.get_event_loop() + return loop.run_until_complete(coro, *args, **kwargs) + + class MockCursor: execute = mock.MagicMock(spec=types.MethodType) execute.__name__ = "execute" @@ -45,6 +51,35 @@ def __exit__(self, *args): return self +class MockAsyncCursor: + def __init__(self, *args, **kwargs): + pass + + # pylint: disable=unused-argument, no-self-use + async def execute(self, query, params=None, throw_exception=False): + if throw_exception: + raise Exception("Test Exception") + + # pylint: disable=unused-argument, no-self-use + async def executemany(self, query, params=None, throw_exception=False): + if throw_exception: + raise Exception("Test Exception") + + # pylint: disable=unused-argument, no-self-use + async def callproc(self, query, params=None, throw_exception=False): + if throw_exception: + raise Exception("Test Exception") + + async def __aenter__(self, *args, **kwargs): + return self + + async def __aexit__(self, *args, **kwargs): + pass + + def close(self): + pass + + class MockConnection: commit = mock.MagicMock(spec=types.MethodType) commit.__name__ = "commit" @@ -64,22 +99,71 @@ def get_dsn_parameters(self): # pylint: disable=no-self-use return {"dbname": "test"} +class MockAsyncConnection: + commit = mock.MagicMock(spec=types.MethodType) + commit.__name__ = "commit" + + rollback = mock.MagicMock(spec=types.MethodType) + rollback.__name__ = "rollback" + + def __init__(self, *args, **kwargs): + self.cursor_factory = kwargs.pop("cursor_factory", None) + pass + + @classmethod + async def connect(*args, **kwargs): + return MockAsyncConnection(**kwargs) + + def cursor(self): + if self.cursor_factory: + cur = self.cursor_factory(self) + print("Returning factory cursor", cur) + return cur + print("Returning MockAsyncCursor") + return MockAsyncCursor() + + def get_dsn_parameters(self): # pylint: disable=no-self-use + return {"dbname": "test"} + + async def __aenter__(self): + return self + + async def __aexit__(self, *args): + return mock.MagicMock(spec=types.MethodType) + + class TestPostgresqlIntegration(TestBase): def setUp(self): super().setUp() self.cursor_mock = mock.patch( "opentelemetry.instrumentation.psycopg.pg_cursor", MockCursor ) + self.cursor_async_mock = mock.patch( + "opentelemetry.instrumentation.psycopg.pg_async_cursor", + MockAsyncCursor, + ) self.connection_mock = mock.patch("psycopg.connect", MockConnection) + self.connection_sync_mock = mock.patch( + "psycopg.Connection.connect", MockConnection + ) + self.connection_async_mock = mock.patch( + "psycopg.AsyncConnection.connect", MockAsyncConnection.connect + ) self.cursor_mock.start() + self.cursor_async_mock.start() self.connection_mock.start() + self.connection_sync_mock.start() + self.connection_async_mock.start() def tearDown(self): super().tearDown() self.memory_exporter.clear() self.cursor_mock.stop() + self.cursor_async_mock.stop() self.connection_mock.stop() + self.connection_sync_mock.stop() + self.connection_async_mock.stop() with self.disable_logging(): PsycopgInstrumentor().uninstrument() @@ -114,6 +198,93 @@ def test_instrumentor(self): spans_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans_list), 1) + # pylint: disable=unused-argument + def test_instrumentor_with_connection_class(self): + PsycopgInstrumentor().instrument() + + cnx = psycopg.Connection.connect(database="test") + + cursor = cnx.cursor() + + query = "SELECT * FROM test" + cursor.execute(query) + + spans_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans_list), 1) + span = spans_list[0] + + # Check version and name in span's instrumentation info + self.assertEqualSpanInstrumentationInfo( + span, opentelemetry.instrumentation.psycopg + ) + + # check that no spans are generated after uninstrument + PsycopgInstrumentor().uninstrument() + + cnx = psycopg.Connection.connect(database="test") + cursor = cnx.cursor() + query = "SELECT * FROM test" + cursor.execute(query) + + spans_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans_list), 1) + + def test_wrap_async_connection_class_with_cursor(self): + PsycopgInstrumentor().instrument() + + async def test_async_connection(): + acnx = await psycopg.AsyncConnection.connect(database="test") + async with acnx as cnx: + async with cnx.cursor() as cursor: + await cursor.execute("SELECT * FROM test") + + async_call(test_async_connection()) + spans_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans_list), 1) + span = spans_list[0] + + # Check version and name in span's instrumentation info + self.assertEqualSpanInstrumentationInfo( + span, opentelemetry.instrumentation.psycopg + ) + + # check that no spans are generated after uninstrument + PsycopgInstrumentor().uninstrument() + + async_call(test_async_connection()) + + spans_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans_list), 1) + + # pylint: disable=unused-argument + async def test_instrumentor_with_async_connection_class(self): + PsycopgInstrumentor().instrument() + + async def test_async_connection(): + acnx = await psycopg.AsyncConnection.connect(database="test") + async with acnx as cnx: + await cnx.execute("SELECT * FROM test") + + import asyncio + + asyncio.run(test_async_connection) + + spans_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans_list), 1) + span = spans_list[0] + + # Check version and name in span's instrumentation info + self.assertEqualSpanInstrumentationInfo( + span, opentelemetry.instrumentation.psycopg + ) + + # check that no spans are generated after uninstrument + PsycopgInstrumentor().uninstrument() + asyncio.run(test_async_connection()) + + spans_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans_list), 1) + def test_span_name(self): PsycopgInstrumentor().instrument() @@ -140,6 +311,34 @@ def test_span_name(self): self.assertEqual(spans_list[4].name, "query") self.assertEqual(spans_list[5].name, "query") + async def test_span_name_async(self): + PsycopgInstrumentor().instrument() + + acnx = psycopg.AsyncConnection.connect(database="test") + async with acnx as cnx: + async with cnx.cursor() as cursor: + await cursor.execute("Test query", ("param1Value", False)) + await cursor.execute( + """multi + line + query""" + ) + await cursor.execute("tab\tseparated query") + await cursor.execute("/* leading comment */ query") + await cursor.execute( + "/* leading comment */ query /* trailing comment */" + ) + await cursor.execute("query /* trailing comment */") + + spans_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans_list), 6) + self.assertEqual(spans_list[0].name, "Test") + self.assertEqual(spans_list[1].name, "multi") + self.assertEqual(spans_list[2].name, "tab") + self.assertEqual(spans_list[3].name, "query") + self.assertEqual(spans_list[4].name, "query") + self.assertEqual(spans_list[5].name, "query") + # pylint: disable=unused-argument def test_not_recording(self): mock_tracer = mock.Mock() @@ -160,6 +359,27 @@ def test_not_recording(self): PsycopgInstrumentor().uninstrument() + # pylint: disable=unused-argument + async def test_not_recording_async(self): + mock_tracer = mock.Mock() + mock_span = mock.Mock() + mock_span.is_recording.return_value = False + mock_tracer.start_span.return_value = mock_span + PsycopgInstrumentor().instrument() + with mock.patch("opentelemetry.trace.get_tracer") as tracer: + tracer.return_value = mock_tracer + acnx = psycopg.AsyncConnection.connect(database="test") + async with acnx as cnx: + async with cnx.cursor() as cursor: + query = "SELECT * FROM test" + cursor.execute(query) + self.assertFalse(mock_span.is_recording()) + self.assertTrue(mock_span.is_recording.called) + self.assertFalse(mock_span.set_attribute.called) + self.assertFalse(mock_span.set_status.called) + + PsycopgInstrumentor().uninstrument() + # pylint: disable=unused-argument def test_custom_tracer_provider(self): resource = resources.Resource.create({}) diff --git a/instrumentation/opentelemetry-instrumentation-psycopg3/LICENSE b/instrumentation/opentelemetry-instrumentation-psycopg3/LICENSE deleted file mode 100644 index 1ef7dad2c5..0000000000 --- a/instrumentation/opentelemetry-instrumentation-psycopg3/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - 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. diff --git a/instrumentation/opentelemetry-instrumentation-psycopg3/README.rst b/instrumentation/opentelemetry-instrumentation-psycopg3/README.rst deleted file mode 100644 index 4224bb675a..0000000000 --- a/instrumentation/opentelemetry-instrumentation-psycopg3/README.rst +++ /dev/null @@ -1,21 +0,0 @@ -OpenTelemetry Psycopg Instrumentation -===================================== - -|pypi| - -.. |pypi| image:: https://badge.fury.io/py/opentelemetry-instrumentation-psycopg3.svg - :target: https://pypi.org/project/opentelemetry-instrumentation-psycopg3/ - -Installation ------------- - -:: - - pip install opentelemetry-instrumentation-psycopg3 - - -References ----------- -* `OpenTelemetry Psycopg Instrumentation `_ -* `OpenTelemetry Project `_ -* `OpenTelemetry Python Examples `_ diff --git a/instrumentation/opentelemetry-instrumentation-psycopg3/pyproject.toml b/instrumentation/opentelemetry-instrumentation-psycopg3/pyproject.toml deleted file mode 100644 index 8764af2209..0000000000 --- a/instrumentation/opentelemetry-instrumentation-psycopg3/pyproject.toml +++ /dev/null @@ -1,58 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "opentelemetry-instrumentation-psycopg3" -dynamic = ["version"] -description = "OpenTelemetry psycopg3 instrumentation" -readme = "README.rst" -license = "Apache-2.0" -requires-python = ">=3.7" -authors = [ - { name = "OpenTelemetry Authors", email = "cncf-opentelemetry-contributors@lists.cncf.io" }, -] -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.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", -] -dependencies = [ - "opentelemetry-api ~= 1.12", - "opentelemetry-instrumentation == 0.44b0.dev", - "opentelemetry-instrumentation-dbapi == 0.44b0.dev", -] - -[project.optional-dependencies] -instruments = [ - "psycopg >= 3.1.17", -] -test = [ - "opentelemetry-instrumentation-psycopg3[instruments]", - "opentelemetry-test-utils == 0.44b0.dev", -] - -[project.entry-points.opentelemetry_instrumentor] -psycopg3 = "opentelemetry.instrumentation.psycopg3:Psycopg3Instrumentor" - -[project.urls] -Homepage = "https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-psycopg3" - -[tool.hatch.version] -path = "src/opentelemetry/instrumentation/psycopg3/version.py" - -[tool.hatch.build.targets.sdist] -include = [ - "/src", - "/tests", -] - -[tool.hatch.build.targets.wheel] -packages = ["src/opentelemetry"] diff --git a/instrumentation/opentelemetry-instrumentation-psycopg3/src/opentelemetry/instrumentation/psycopg3/__init__.py b/instrumentation/opentelemetry-instrumentation-psycopg3/src/opentelemetry/instrumentation/psycopg3/__init__.py deleted file mode 100644 index e60ecb81f8..0000000000 --- a/instrumentation/opentelemetry-instrumentation-psycopg3/src/opentelemetry/instrumentation/psycopg3/__init__.py +++ /dev/null @@ -1,344 +0,0 @@ -# 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. - -""" -The integration with PostgreSQL supports the `Psycopg`_ library, it can be enabled by -using ``Psycopg3Instrumentor``. - -.. _Psycopg: http://initd.org/psycopg/ - -SQLCOMMENTER -***************************************** -You can optionally configure Psycopg3 instrumentation to enable sqlcommenter which enriches -the query with contextual information. - -Usage ------ - -.. code:: python - - from opentelemetry.instrumentation.psycopg3 import Psycopg3Instrumentor - - Psycopg3Instrumentor().instrument(enable_commenter=True, commenter_options={}) - - -For example, -:: - - Invoking cursor.execute("select * from auth_users") 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 /*tag=value*/;" - - -SQLCommenter Configurations -*************************** -We can configure the tags to be appended to the sqlquery log by adding configuration inside commenter_options(default:{}) keyword - -db_driver = True(Default) or False - -For example, -:: -Enabling this flag will add psycopg3 - -dbapi_threadsafety = True(Default) or False - -For example, -:: -Enabling this flag will add threadsafety /*dbapi_threadsafety=2*/ - -dbapi_level = True(Default) or False - -For example, -:: -Enabling this flag will add dbapi_level /*dbapi_level='2.0'*/ - -libpq_version = True(Default) or False - -For example, -:: -Enabling this flag will add libpq_version /*libpq_version=140001*/ - -driver_paramstyle = True(Default) or False - -For example, -:: -Enabling this flag will add driver_paramstyle /*driver_paramstyle='pyformat'*/ - -opentelemetry_values = True(Default) or False - -For example, -:: -Enabling this flag will add traceparent values /*traceparent='00-03afa25236b8cd948fa853d67038ac79-405ff022e8247c46-01'*/ - -Usage ------ - -.. code-block:: python - - import psycopg - from opentelemetry.instrumentation.psycopg3 import Psycopg3Instrumentor - - - Psycopg3Instrumentor().instrument() - - cnx = psycopg3.connect(database='Database') - cursor = cnx.cursor() - cursor.execute("INSERT INTO test (testField) VALUES (123)") - cursor.close() - cnx.close() - -API ---- -""" - -import logging -import typing -from typing import Collection - -import psycopg -from psycopg import AsyncCursor as pg_async_cursor -from psycopg import Cursor as pg_cursor -from psycopg.sql import Composed # pylint: disable=no-name-in-module - -from opentelemetry.instrumentation import dbapi -from opentelemetry.instrumentation.instrumentor import BaseInstrumentor -from opentelemetry.instrumentation.psycopg3.package import _instruments -from opentelemetry.instrumentation.psycopg3.version import __version__ - -_logger = logging.getLogger(__name__) -_OTEL_CURSOR_FACTORY_KEY = "_otel_orig_cursor_factory" - - -class Psycopg3Instrumentor(BaseInstrumentor): - _CONNECTION_ATTRIBUTES = { - "database": "info.dbname", - "port": "info.port", - "host": "info.host", - "user": "info.user", - } - - _DATABASE_SYSTEM = "postgresql" - - def instrumentation_dependencies(self) -> Collection[str]: - return _instruments - - def _instrument(self, **kwargs): - """Integrate with PostgreSQL Psycopg library. - Psycopg: http://initd.org/psycopg/ - """ - tracer_provider = kwargs.get("tracer_provider") - enable_sqlcommenter = kwargs.get("enable_commenter", False) - commenter_options = kwargs.get("commenter_options", {}) - - dbapi.wrap_connect( - __name__, - psycopg, - "connect", - self._DATABASE_SYSTEM, - self._CONNECTION_ATTRIBUTES, - version=__version__, - tracer_provider=tracer_provider, - db_api_integration_factory=DatabaseApiIntegration, - enable_commenter=enable_sqlcommenter, - commenter_options=commenter_options, - ) - - dbapi.wrap_connect( - __name__, - psycopg.Connection, - "connect", - self._DATABASE_SYSTEM, - self._CONNECTION_ATTRIBUTES, - version=__version__, - tracer_provider=tracer_provider, - db_api_integration_factory=DatabaseApiIntegration, - enable_commenter=enable_sqlcommenter, - commenter_options=commenter_options, - ) - dbapi.wrap_connect( - __name__, - psycopg.AsyncConnection, - "connect", - self._DATABASE_SYSTEM, - self._CONNECTION_ATTRIBUTES, - version=__version__, - tracer_provider=tracer_provider, - db_api_integration_factory=DatabaseApiAsyncIntegration, - enable_commenter=enable_sqlcommenter, - commenter_options=commenter_options, - ) - - def _uninstrument(self, **kwargs): - """ "Disable Psycopg3 instrumentation""" - dbapi.unwrap_connect(psycopg, "connect") - dbapi.unwrap_connect(psycopg.Connection, "connect") - dbapi.unwrap_connect(psycopg.AsyncConnection, "connect") - - # TODO(owais): check if core dbapi can do this for all dbapi implementations e.g, pymysql and mysql - @staticmethod - def instrument_connection(connection, tracer_provider=None): - if not hasattr(connection, "_is_instrumented_by_opentelemetry"): - connection._is_instrumented_by_opentelemetry = False - - if not connection._is_instrumented_by_opentelemetry: - setattr( - connection, _OTEL_CURSOR_FACTORY_KEY, connection.cursor_factory - ) - connection.cursor_factory = _new_cursor_factory( - tracer_provider=tracer_provider - ) - connection._is_instrumented_by_opentelemetry = True - else: - _logger.warning( - "Attempting to instrument Psycopg connection while already instrumented" - ) - return connection - - # TODO(owais): check if core dbapi can do this for all dbapi implementations e.g, pymysql and mysql - @staticmethod - def uninstrument_connection(connection): - connection.cursor_factory = getattr( - connection, _OTEL_CURSOR_FACTORY_KEY, None - ) - - return connection - - -# TODO(owais): check if core dbapi can do this for all dbapi implementations e.g, pymysql and mysql -class DatabaseApiIntegration(dbapi.DatabaseApiIntegration): - def wrapped_connection( - self, - connect_method: typing.Callable[..., typing.Any], - args: typing.Tuple[typing.Any, typing.Any], - kwargs: typing.Dict[typing.Any, typing.Any], - ): - """Add object proxy to connection object.""" - base_cursor_factory = kwargs.pop("cursor_factory", None) - new_factory_kwargs = {"db_api": self} - if base_cursor_factory: - new_factory_kwargs["base_factory"] = base_cursor_factory - kwargs["cursor_factory"] = _new_cursor_factory(**new_factory_kwargs) - connection = connect_method(*args, **kwargs) - self.get_connection_attributes(connection) - return connection - - -class DatabaseApiAsyncIntegration(dbapi.DatabaseApiIntegration): - async def wrapped_connection( - self, - connect_method: typing.Callable[..., typing.Any], - args: typing.Tuple[typing.Any, typing.Any], - kwargs: typing.Dict[typing.Any, typing.Any], - ): - """Add object proxy to connection object.""" - base_cursor_factory = kwargs.pop("cursor_factory", None) - new_factory_kwargs = {"db_api": self} - if base_cursor_factory: - new_factory_kwargs["base_factory"] = base_cursor_factory - kwargs["cursor_factory"] = _new_cursor_async_factory( - **new_factory_kwargs - ) - connection = await connect_method(*args, **kwargs) - self.get_connection_attributes(connection) - return connection - - -class CursorTracer(dbapi.CursorTracer): - def get_operation_name(self, cursor, args): - if not args: - return "" - - statement = args[0] - if isinstance(statement, Composed): - statement = statement.as_string(cursor) - - if isinstance(statement, str): - # Strip leading comments so we get the operation name. - return self._leading_comment_remover.sub("", statement).split()[0] - - return "" - - def get_statement(self, cursor, args): - if not args: - return "" - - statement = args[0] - if isinstance(statement, Composed): - statement = statement.as_string(cursor) - - return statement - - -def _new_cursor_factory(db_api=None, base_factory=None, tracer_provider=None): - if not db_api: - db_api = DatabaseApiIntegration( - __name__, - Psycopg3Instrumentor._DATABASE_SYSTEM, - connection_attributes=Psycopg3Instrumentor._CONNECTION_ATTRIBUTES, - version=__version__, - tracer_provider=tracer_provider, - ) - - base_factory = base_factory or pg_cursor - _cursor_tracer = CursorTracer(db_api) - - class TracedCursorFactory(base_factory): - def execute(self, *args, **kwargs): - return _cursor_tracer.traced_execution( - self, super().execute, *args, **kwargs - ) - - def executemany(self, *args, **kwargs): - return _cursor_tracer.traced_execution( - self, super().executemany, *args, **kwargs - ) - - def callproc(self, *args, **kwargs): - return _cursor_tracer.traced_execution( - self, super().callproc, *args, **kwargs - ) - - return TracedCursorFactory - - -def _new_cursor_async_factory( - db_api=None, base_factory=None, tracer_provider=None -): - if not db_api: - db_api = DatabaseApiAsyncIntegration( - __name__, - Psycopg3Instrumentor._DATABASE_SYSTEM, - connection_attributes=Psycopg3Instrumentor._CONNECTION_ATTRIBUTES, - version=__version__, - tracer_provider=tracer_provider, - ) - base_factory = base_factory or pg_async_cursor - _cursor_tracer = CursorTracer(db_api) - - class TracedCursorAsyncFactory(base_factory): - async def execute(self, *args, **kwargs): - return await _cursor_tracer.traced_execution( - self, super().execute, *args, **kwargs - ) - - async def executemany(self, *args, **kwargs): - return await _cursor_tracer.traced_execution( - self, super().executemany, *args, **kwargs - ) - - async def callproc(self, *args, **kwargs): - return await _cursor_tracer.traced_execution( - self, super().callproc, *args, **kwargs - ) - - return TracedCursorAsyncFactory diff --git a/instrumentation/opentelemetry-instrumentation-psycopg3/src/opentelemetry/instrumentation/psycopg3/package.py b/instrumentation/opentelemetry-instrumentation-psycopg3/src/opentelemetry/instrumentation/psycopg3/package.py deleted file mode 100644 index a1fdd826cf..0000000000 --- a/instrumentation/opentelemetry-instrumentation-psycopg3/src/opentelemetry/instrumentation/psycopg3/package.py +++ /dev/null @@ -1,16 +0,0 @@ -# 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. - - -_instruments = ("psycopg >= 3.1.12",) diff --git a/instrumentation/opentelemetry-instrumentation-psycopg3/src/opentelemetry/instrumentation/psycopg3/version.py b/instrumentation/opentelemetry-instrumentation-psycopg3/src/opentelemetry/instrumentation/psycopg3/version.py deleted file mode 100644 index ff896307c3..0000000000 --- a/instrumentation/opentelemetry-instrumentation-psycopg3/src/opentelemetry/instrumentation/psycopg3/version.py +++ /dev/null @@ -1,15 +0,0 @@ -# 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.44b0.dev" diff --git a/instrumentation/opentelemetry-instrumentation-psycopg3/tests/__init__.py b/instrumentation/opentelemetry-instrumentation-psycopg3/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/instrumentation/opentelemetry-instrumentation-psycopg3/tests/test_psycopg3_integration.py b/instrumentation/opentelemetry-instrumentation-psycopg3/tests/test_psycopg3_integration.py deleted file mode 100644 index 38df62d73d..0000000000 --- a/instrumentation/opentelemetry-instrumentation-psycopg3/tests/test_psycopg3_integration.py +++ /dev/null @@ -1,491 +0,0 @@ -# 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 -import types -from unittest import mock - -import psycopg - -import opentelemetry.instrumentation.psycopg3 -from opentelemetry.instrumentation.psycopg3 import Psycopg3Instrumentor -from opentelemetry.sdk import resources -from opentelemetry.test.test_base import TestBase - - -def async_call(coro, *args, **kwargs): - loop = asyncio.get_event_loop() - return loop.run_until_complete(coro, *args, **kwargs) - - -class MockCursor: - execute = mock.MagicMock(spec=types.MethodType) - execute.__name__ = "execute" - - executemany = mock.MagicMock(spec=types.MethodType) - executemany.__name__ = "executemany" - - callproc = mock.MagicMock(spec=types.MethodType) - callproc.__name__ = "callproc" - - rowcount = "SomeRowCount" - - def __init__(self, *args, **kwargs): - pass - - def __enter__(self): - return self - - def __exit__(self, *args): - return self - - -class MockAsyncCursor: - def __init__(self, *args, **kwargs): - pass - - # pylint: disable=unused-argument, no-self-use - async def execute(self, query, params=None, throw_exception=False): - if throw_exception: - raise Exception("Test Exception") - - # pylint: disable=unused-argument, no-self-use - async def executemany(self, query, params=None, throw_exception=False): - if throw_exception: - raise Exception("Test Exception") - - # pylint: disable=unused-argument, no-self-use - async def callproc(self, query, params=None, throw_exception=False): - if throw_exception: - raise Exception("Test Exception") - - async def __aenter__(self, *args, **kwargs): - return self - - async def __aexit__(self, *args, **kwargs): - pass - - def close(self): - pass - - -class MockConnection: - commit = mock.MagicMock(spec=types.MethodType) - commit.__name__ = "commit" - - rollback = mock.MagicMock(spec=types.MethodType) - rollback.__name__ = "rollback" - - def __init__(self, *args, **kwargs): - self.cursor_factory = kwargs.pop("cursor_factory", None) - - def cursor(self): - if self.cursor_factory: - return self.cursor_factory(self) - return MockCursor() - - def get_dsn_parameters(self): # pylint: disable=no-self-use - return {"dbname": "test"} - - -class MockAsyncConnection: - commit = mock.MagicMock(spec=types.MethodType) - commit.__name__ = "commit" - - rollback = mock.MagicMock(spec=types.MethodType) - rollback.__name__ = "rollback" - - def __init__(self, *args, **kwargs): - self.cursor_factory = kwargs.pop("cursor_factory", None) - pass - - @classmethod - async def connect(*args, **kwargs): - return MockAsyncConnection(**kwargs) - - def cursor(self): - if self.cursor_factory: - cur = self.cursor_factory(self) - print("Returning factory cursor", cur) - return cur - print("Returning MockAsyncCursor") - return MockAsyncCursor() - - def get_dsn_parameters(self): # pylint: disable=no-self-use - return {"dbname": "test"} - - async def __aenter__(self): - return self - - async def __aexit__(self, *args): - return mock.MagicMock(spec=types.MethodType) - - -class TestPostgresqlIntegration(TestBase): - def setUp(self): - super().setUp() - self.cursor_mock = mock.patch( - "opentelemetry.instrumentation.psycopg3.pg_cursor", MockCursor - ) - self.cursor_async_mock = mock.patch( - "opentelemetry.instrumentation.psycopg3.pg_async_cursor", - MockAsyncCursor, - ) - self.connection_mock = mock.patch("psycopg.connect", MockConnection) - self.connection_sync_mock = mock.patch( - "psycopg.Connection.connect", MockConnection - ) - self.connection_async_mock = mock.patch( - "psycopg.AsyncConnection.connect", MockAsyncConnection.connect - ) - - self.cursor_mock.start() - self.cursor_async_mock.start() - self.connection_mock.start() - self.connection_sync_mock.start() - self.connection_async_mock.start() - - def tearDown(self): - super().tearDown() - self.memory_exporter.clear() - self.cursor_mock.stop() - self.cursor_async_mock.stop() - self.connection_mock.stop() - self.connection_sync_mock.stop() - self.connection_async_mock.stop() - with self.disable_logging(): - Psycopg3Instrumentor().uninstrument() - - # pylint: disable=unused-argument - def test_instrumentor(self): - Psycopg3Instrumentor().instrument() - - cnx = psycopg.connect(database="test") - - cursor = cnx.cursor() - - query = "SELECT * FROM test" - cursor.execute(query) - - spans_list = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans_list), 1) - span = spans_list[0] - - # Check version and name in span's instrumentation info - self.assertEqualSpanInstrumentationInfo( - span, opentelemetry.instrumentation.psycopg3 - ) - - # check that no spans are generated after uninstrument - Psycopg3Instrumentor().uninstrument() - - cnx = psycopg.connect(database="test") - cursor = cnx.cursor() - query = "SELECT * FROM test" - cursor.execute(query) - - spans_list = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans_list), 1) - - # pylint: disable=unused-argument - def test_instrumentor_with_connection_class(self): - Psycopg3Instrumentor().instrument() - - cnx = psycopg.Connection.connect(database="test") - - cursor = cnx.cursor() - - query = "SELECT * FROM test" - cursor.execute(query) - - spans_list = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans_list), 1) - span = spans_list[0] - - # Check version and name in span's instrumentation info - self.assertEqualSpanInstrumentationInfo( - span, opentelemetry.instrumentation.psycopg3 - ) - - # check that no spans are generated after uninstrument - Psycopg3Instrumentor().uninstrument() - - cnx = psycopg.Connection.connect(database="test") - cursor = cnx.cursor() - query = "SELECT * FROM test" - cursor.execute(query) - - spans_list = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans_list), 1) - - def test_wrap_async_connection_class_with_cursor(self): - Psycopg3Instrumentor().instrument() - - async def test_async_connection(): - acnx = await psycopg.AsyncConnection.connect(database="test") - async with acnx as cnx: - async with cnx.cursor() as cursor: - await cursor.execute("SELECT * FROM test") - - async_call(test_async_connection()) - spans_list = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans_list), 1) - span = spans_list[0] - - # Check version and name in span's instrumentation info - self.assertEqualSpanInstrumentationInfo( - span, opentelemetry.instrumentation.psycopg3 - ) - - # check that no spans are generated after uninstrument - Psycopg3Instrumentor().uninstrument() - - async_call(test_async_connection()) - - spans_list = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans_list), 1) - - # pylint: disable=unused-argument - async def test_instrumentor_with_async_connection_class(self): - Psycopg3Instrumentor().instrument() - - async def test_async_connection(): - acnx = await psycopg.AsyncConnection.connect(database="test") - async with acnx as cnx: - await cnx.execute("SELECT * FROM test") - - import asyncio - - asyncio.run(test_async_connection) - - spans_list = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans_list), 1) - span = spans_list[0] - - # Check version and name in span's instrumentation info - self.assertEqualSpanInstrumentationInfo( - span, opentelemetry.instrumentation.psycopg3 - ) - - # check that no spans are generated after uninstrument - Psycopg3Instrumentor().uninstrument() - asyncio.run(test_async_connection()) - - spans_list = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans_list), 1) - - def test_span_name(self): - Psycopg3Instrumentor().instrument() - - cnx = psycopg.connect(database="test") - - cursor = cnx.cursor() - - cursor.execute("Test query", ("param1Value", False)) - cursor.execute( - """multi - line - query""" - ) - cursor.execute("tab\tseparated query") - cursor.execute("/* leading comment */ query") - cursor.execute("/* leading comment */ query /* trailing comment */") - cursor.execute("query /* trailing comment */") - spans_list = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans_list), 6) - self.assertEqual(spans_list[0].name, "Test") - self.assertEqual(spans_list[1].name, "multi") - self.assertEqual(spans_list[2].name, "tab") - self.assertEqual(spans_list[3].name, "query") - self.assertEqual(spans_list[4].name, "query") - self.assertEqual(spans_list[5].name, "query") - - async def test_span_name_async(self): - Psycopg3Instrumentor().instrument() - - acnx = psycopg.AsyncConnection.connect(database="test") - async with acnx as cnx: - async with cnx.cursor() as cursor: - await cursor.execute("Test query", ("param1Value", False)) - await cursor.execute( - """multi - line - query""" - ) - await cursor.execute("tab\tseparated query") - await cursor.execute("/* leading comment */ query") - await cursor.execute( - "/* leading comment */ query /* trailing comment */" - ) - await cursor.execute("query /* trailing comment */") - - spans_list = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans_list), 6) - self.assertEqual(spans_list[0].name, "Test") - self.assertEqual(spans_list[1].name, "multi") - self.assertEqual(spans_list[2].name, "tab") - self.assertEqual(spans_list[3].name, "query") - self.assertEqual(spans_list[4].name, "query") - self.assertEqual(spans_list[5].name, "query") - - # pylint: disable=unused-argument - def test_not_recording(self): - mock_tracer = mock.Mock() - mock_span = mock.Mock() - mock_span.is_recording.return_value = False - mock_tracer.start_span.return_value = mock_span - Psycopg3Instrumentor().instrument() - with mock.patch("opentelemetry.trace.get_tracer") as tracer: - tracer.return_value = mock_tracer - cnx = psycopg.connect(database="test") - cursor = cnx.cursor() - query = "SELECT * FROM test" - cursor.execute(query) - self.assertFalse(mock_span.is_recording()) - self.assertTrue(mock_span.is_recording.called) - self.assertFalse(mock_span.set_attribute.called) - self.assertFalse(mock_span.set_status.called) - - Psycopg3Instrumentor().uninstrument() - - # pylint: disable=unused-argument - async def test_not_recording_async(self): - mock_tracer = mock.Mock() - mock_span = mock.Mock() - mock_span.is_recording.return_value = False - mock_tracer.start_span.return_value = mock_span - Psycopg3Instrumentor().instrument() - with mock.patch("opentelemetry.trace.get_tracer") as tracer: - tracer.return_value = mock_tracer - acnx = psycopg.AsyncConnection.connect(database="test") - async with acnx as cnx: - async with cnx.cursor() as cursor: - query = "SELECT * FROM test" - cursor.execute(query) - self.assertFalse(mock_span.is_recording()) - self.assertTrue(mock_span.is_recording.called) - self.assertFalse(mock_span.set_attribute.called) - self.assertFalse(mock_span.set_status.called) - - Psycopg3Instrumentor().uninstrument() - - # pylint: disable=unused-argument - def test_custom_tracer_provider(self): - resource = resources.Resource.create({}) - result = self.create_tracer_provider(resource=resource) - tracer_provider, exporter = result - - Psycopg3Instrumentor().instrument(tracer_provider=tracer_provider) - - cnx = psycopg.connect(database="test") - cursor = cnx.cursor() - query = "SELECT * FROM test" - cursor.execute(query) - - spans_list = exporter.get_finished_spans() - self.assertEqual(len(spans_list), 1) - span = spans_list[0] - - self.assertIs(span.resource, resource) - - # pylint: disable=unused-argument - def test_instrument_connection(self): - cnx = psycopg.connect(database="test") - query = "SELECT * FROM test" - cursor = cnx.cursor() - cursor.execute(query) - - spans_list = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans_list), 0) - - cnx = Psycopg3Instrumentor().instrument_connection(cnx) - cursor = cnx.cursor() - cursor.execute(query) - - spans_list = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans_list), 1) - - # pylint: disable=unused-argument - def test_instrument_connection_with_instrument(self): - cnx = psycopg.connect(database="test") - query = "SELECT * FROM test" - cursor = cnx.cursor() - cursor.execute(query) - - spans_list = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans_list), 0) - - Psycopg3Instrumentor().instrument() - cnx = Psycopg3Instrumentor().instrument_connection(cnx) - cursor = cnx.cursor() - cursor.execute(query) - - spans_list = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans_list), 1) - - # pylint: disable=unused-argument - def test_uninstrument_connection_with_instrument(self): - Psycopg3Instrumentor().instrument() - cnx = psycopg.connect(database="test") - query = "SELECT * FROM test" - cursor = cnx.cursor() - cursor.execute(query) - - spans_list = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans_list), 1) - - cnx = Psycopg3Instrumentor().uninstrument_connection(cnx) - cursor = cnx.cursor() - cursor.execute(query) - - spans_list = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans_list), 1) - - # pylint: disable=unused-argument - def test_uninstrument_connection_with_instrument_connection(self): - cnx = psycopg.connect(database="test") - Psycopg3Instrumentor().instrument_connection(cnx) - query = "SELECT * FROM test" - cursor = cnx.cursor() - cursor.execute(query) - - spans_list = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans_list), 1) - - cnx = Psycopg3Instrumentor().uninstrument_connection(cnx) - cursor = cnx.cursor() - cursor.execute(query) - - spans_list = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans_list), 1) - - @mock.patch("opentelemetry.instrumentation.dbapi.wrap_connect") - def test_sqlcommenter_enabled(self, event_mocked): - cnx = psycopg.connect(database="test") - Psycopg3Instrumentor().instrument(enable_commenter=True) - query = "SELECT * FROM test" - cursor = cnx.cursor() - cursor.execute(query) - kwargs = event_mocked.call_args[1] - self.assertEqual(kwargs["enable_commenter"], True) - - @mock.patch("opentelemetry.instrumentation.dbapi.wrap_connect") - def test_sqlcommenter_disabled(self, event_mocked): - cnx = psycopg.connect(database="test") - Psycopg3Instrumentor().instrument() - query = "SELECT * FROM test" - cursor = cnx.cursor() - cursor.execute(query) - kwargs = event_mocked.call_args[1] - self.assertEqual(kwargs["enable_commenter"], False) diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py index 3e0db23e12..3591581c97 100644 --- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py @@ -124,10 +124,6 @@ "library": "psycopg2 >= 2.7.3.1", "instrumentation": "opentelemetry-instrumentation-psycopg2==0.45b0.dev", }, - { - "library": "psycopg >= 3.1.17", - "instrumentation": "opentelemetry-instrumentation-psycopg3==0.44b0.dev", - }, { "library": "pymemcache >= 1.3.5, < 5", "instrumentation": "opentelemetry-instrumentation-pymemcache==0.45b0.dev", diff --git a/tox.ini b/tox.ini index fb63f37f78..efc53e587c 100644 --- a/tox.ini +++ b/tox.ini @@ -434,7 +434,7 @@ commands_pre = psycopg: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-dbapi {toxinidir}/instrumentation/opentelemetry-instrumentation-psycopg[test] psycopg2: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-dbapi {toxinidir}/instrumentation/opentelemetry-instrumentation-psycopg2[test] - + pymysql: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-dbapi {toxinidir}/instrumentation/opentelemetry-instrumentation-pymysql[test] pyramid: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-pyramid[test]