From 57e182a3f91cf23789c62d0a50073188f0bf303e Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Tue, 6 Jul 2021 14:52:51 +0100 Subject: [PATCH 01/19] test removing thread specific logic. upgrade to sqlalchemy ~1.4 --- .../instrumentation/sqlalchemy/engine.py | 22 ++++++++++--------- tox.ini | 4 ++-- 2 files changed, 14 insertions(+), 12 deletions(-) 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 e69c6dbcb4..6adf48cd26 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from threading import local +# from threading import local from sqlalchemy.event import listen # pylint: disable=no-name-in-module @@ -60,19 +60,19 @@ def __init__(self, tracer, engine): self.engine = engine self.vendor = _normalize_vendor(engine.name) self.cursor_mapping = {} - self.local = local() + # self.local = local() listen(engine, "before_cursor_execute", self._before_cur_exec) listen(engine, "after_cursor_execute", self._after_cur_exec) listen(engine, "handle_error", self._handle_error) - @property - def current_thread_span(self): - return getattr(self.local, "current_span", None) + # @property + # def current_thread_span(self): + # return getattr(self.local, "current_span", None) - @current_thread_span.setter - def current_thread_span(self, span): - setattr(self.local, "current_span", span) + # @current_thread_span.setter + # def current_thread_span(self, span): + # setattr(self.local, "current_span", span) def _operation_name(self, db_name, statement): parts = [] @@ -100,7 +100,8 @@ def _before_cur_exec(self, conn, cursor, statement, *args): self._operation_name(db_name, statement), kind=trace.SpanKind.CLIENT, ) - self.current_thread_span = self.cursor_mapping[cursor] = span + # self.current_thread_span = + self.cursor_mapping[cursor] = span with trace.use_span(span, end_on_exit=False): if span.is_recording(): span.set_attribute(SpanAttributes.DB_STATEMENT, statement) @@ -118,7 +119,8 @@ def _after_cur_exec(self, conn, cursor, statement, *args): self._cleanup(cursor) def _handle_error(self, context): - span = self.current_thread_span + span = self.cursor_mapping[context.cursor] + # span = self.current_thread_span if span is None: return diff --git a/tox.ini b/tox.ini index 2d58b8928e..f720b8fd43 100644 --- a/tox.ini +++ b/tox.ini @@ -329,7 +329,7 @@ commands = [testenv:lint] basepython: python3.9 -recreate = False +recreate = False deps = -c dev-requirements.txt flaky @@ -399,7 +399,7 @@ deps = PyMySQL ~= 0.10.1 psycopg2 ~= 2.8.4 aiopg >= 0.13.0, < 1.3.0 - sqlalchemy ~= 1.3.16 + sqlalchemy ~= 1.4 redis ~= 3.3.11 celery[pytest] >= 4.0, < 6.0 protobuf>=3.13.0 From 8d58c2b6d09ab89e62ef48a1ce861ebaa0c1b831 Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Tue, 6 Jul 2021 15:52:51 +0100 Subject: [PATCH 02/19] wip --- .../instrumentation/sqlalchemy/engine.py | 22 +++++++++---------- .../tests/sqlalchemy_tests/mixins.py | 2 ++ .../tests/sqlalchemy_tests/test_sqlite.py | 2 +- 3 files changed, 14 insertions(+), 12 deletions(-) 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 6adf48cd26..ab8f84bd07 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# from threading import local +from threading import local from sqlalchemy.event import listen # pylint: disable=no-name-in-module @@ -60,19 +60,19 @@ def __init__(self, tracer, engine): self.engine = engine self.vendor = _normalize_vendor(engine.name) self.cursor_mapping = {} - # self.local = local() + self.local = local() listen(engine, "before_cursor_execute", self._before_cur_exec) listen(engine, "after_cursor_execute", self._after_cur_exec) listen(engine, "handle_error", self._handle_error) - # @property - # def current_thread_span(self): - # return getattr(self.local, "current_span", None) + @property + def current_thread_span(self): + return getattr(self.local, "current_span", None) - # @current_thread_span.setter - # def current_thread_span(self, span): - # setattr(self.local, "current_span", span) + @current_thread_span.setter + def current_thread_span(self, span): + setattr(self.local, "current_span", span) def _operation_name(self, db_name, statement): parts = [] @@ -100,7 +100,7 @@ def _before_cur_exec(self, conn, cursor, statement, *args): self._operation_name(db_name, statement), kind=trace.SpanKind.CLIENT, ) - # self.current_thread_span = + self.current_thread_span = span self.cursor_mapping[cursor] = span with trace.use_span(span, end_on_exit=False): if span.is_recording(): @@ -119,8 +119,8 @@ def _after_cur_exec(self, conn, cursor, statement, *args): self._cleanup(cursor) def _handle_error(self, context): - span = self.cursor_mapping[context.cursor] - # span = self.current_thread_span + # span = self.cursor_mapping[context.cursor] + span = self.current_thread_span if span is None: return diff --git a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py index c2e5548ab1..04db609886 100644 --- a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py +++ b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py @@ -15,6 +15,7 @@ import contextlib import logging import threading +import unittest from sqlalchemy import Column, Integer, String, create_engine, insert from sqlalchemy.ext.declarative import declarative_base @@ -242,4 +243,5 @@ def insert_players(session): close_all_sessions() spans = self.memory_exporter.get_finished_spans() + breakpoint() self.assertEqual(len(spans), 5) 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 0acba0fec2..981e82d7c2 100644 --- a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_sqlite.py +++ b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_sqlite.py @@ -35,7 +35,7 @@ class SQLiteTestCase(SQLAlchemyTestMixin): def test_engine_execute_errors(self): # ensures that SQL errors are reported stmt = "SELECT * FROM a_wrong_table" - with pytest.raises(OperationalError): + with pytest.raises(Exception): with self.connection() as conn: conn.execute(stmt).fetchall() From 51c53fff7d970478d7b332e27fddebe95db85466 Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Tue, 6 Jul 2021 16:13:36 +0100 Subject: [PATCH 03/19] add _span to sqlalchemy execution context instead of maintaining mapping --- .../instrumentation/sqlalchemy/engine.py | 32 ++++--------------- .../tests/sqlalchemy_tests/mixins.py | 1 - .../tests/sqlalchemy_tests/test_sqlite.py | 2 +- 3 files changed, 7 insertions(+), 28 deletions(-) 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 ab8f84bd07..b1297e9bcb 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py @@ -12,8 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from threading import local - from sqlalchemy.event import listen # pylint: disable=no-name-in-module from opentelemetry import trace @@ -59,21 +57,11 @@ def __init__(self, tracer, engine): self.tracer = tracer self.engine = engine self.vendor = _normalize_vendor(engine.name) - self.cursor_mapping = {} - self.local = local() listen(engine, "before_cursor_execute", self._before_cur_exec) listen(engine, "after_cursor_execute", self._after_cur_exec) listen(engine, "handle_error", self._handle_error) - @property - def current_thread_span(self): - return getattr(self.local, "current_span", None) - - @current_thread_span.setter - def current_thread_span(self, span): - setattr(self.local, "current_span", span) - def _operation_name(self, db_name, statement): parts = [] if isinstance(statement, str): @@ -90,7 +78,7 @@ def _operation_name(self, db_name, statement): return " ".join(parts) # pylint: disable=unused-argument - def _before_cur_exec(self, conn, cursor, statement, *args): + def _before_cur_exec(self, conn, cursor, statement, params, context, executemany): attrs, found = _get_attributes_from_url(conn.engine.url) if not found: attrs = _get_attributes_from_cursor(self.vendor, cursor, attrs) @@ -100,8 +88,6 @@ def _before_cur_exec(self, conn, cursor, statement, *args): self._operation_name(db_name, statement), kind=trace.SpanKind.CLIENT, ) - self.current_thread_span = span - self.cursor_mapping[cursor] = span with trace.use_span(span, end_on_exit=False): if span.is_recording(): span.set_attribute(SpanAttributes.DB_STATEMENT, statement) @@ -109,18 +95,18 @@ def _before_cur_exec(self, conn, cursor, statement, *args): for key, value in attrs.items(): span.set_attribute(key, value) + context._span = span + # pylint: disable=unused-argument - def _after_cur_exec(self, conn, cursor, statement, *args): - span = self.cursor_mapping.get(cursor, None) + def _after_cur_exec(self, conn, cursor, statement, params, context, executemany): + span = getattr(context, '_span', None) if span is None: return span.end() - self._cleanup(cursor) def _handle_error(self, context): - # span = self.cursor_mapping[context.cursor] - span = self.current_thread_span + span = getattr(context.execution_context, '_span', None) if span is None: return @@ -131,13 +117,7 @@ def _handle_error(self, context): ) finally: span.end() - self._cleanup(context.cursor) - def _cleanup(self, cursor): - try: - del self.cursor_mapping[cursor] - except KeyError: - pass def _get_attributes_from_url(url): diff --git a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py index 04db609886..a3bd18ddf7 100644 --- a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py +++ b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py @@ -243,5 +243,4 @@ def insert_players(session): close_all_sessions() spans = self.memory_exporter.get_finished_spans() - breakpoint() self.assertEqual(len(spans), 5) 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 981e82d7c2..0acba0fec2 100644 --- a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_sqlite.py +++ b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_sqlite.py @@ -35,7 +35,7 @@ class SQLiteTestCase(SQLAlchemyTestMixin): def test_engine_execute_errors(self): # ensures that SQL errors are reported stmt = "SELECT * FROM a_wrong_table" - with pytest.raises(Exception): + with pytest.raises(OperationalError): with self.connection() as conn: conn.execute(stmt).fetchall() From 3d4087c29bb33025efa6fc8a1c0992055ba88885 Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Tue, 6 Jul 2021 17:07:26 +0100 Subject: [PATCH 04/19] account for add_all optimisations in sqlalchemy 1.4 --- .../opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py index a3bd18ddf7..c5fdd925ab 100644 --- a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py +++ b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py @@ -243,4 +243,4 @@ def insert_players(session): close_all_sessions() spans = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans), 5) + self.assertEqual(len(spans), 5 if self.VENDOR not in ["postgresql"] else 3) From 4a723bbdc037cd62046b9b177efac0c589588677 Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Tue, 6 Jul 2021 17:48:11 +0100 Subject: [PATCH 05/19] update docstring --- .../instrumentation/sqlalchemy/__init__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 01db312b3f..de85174e36 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/__init__.py @@ -36,6 +36,18 @@ engine=engine, ) + # of the async variant of SQLAlchemy + + from sqlalchemy.ext.asyncio import create_async_engine + + from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor + import sqlalchemy + + engine = create_async_engine("sqlite:///:memory:") + SQLAlchemyInstrumentor().instrument( + engine=engine.sync_engine + ) + API --- """ From 5ade34e0322688ce742c2b4432e3a4316135c5fb Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Wed, 7 Jul 2021 10:20:46 +0100 Subject: [PATCH 06/19] add tests for async. fix black issues --- .../instrumentation/sqlalchemy/engine.py | 5 ++- .../instrumentation/sqlalchemy/package.py | 2 +- .../tests/test_sqlalchemy.py | 33 +++++++++++++++++-- .../tests/sqlalchemy_tests/test_mssql.py | 3 +- .../tests/sqlalchemy_tests/test_mysql.py | 3 +- .../tests/sqlalchemy_tests/test_postgres.py | 3 +- .../tests/sqlalchemy_tests/test_sqlite.py | 3 +- tox.ini | 9 +++-- 8 files changed, 49 insertions(+), 12 deletions(-) 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 b1297e9bcb..a924da7276 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py @@ -113,7 +113,10 @@ def _handle_error(self, context): try: if span.is_recording(): span.set_status( - Status(StatusCode.ERROR, str(context.original_exception),) + Status( + StatusCode.ERROR, + str(context.original_exception), + ) ) finally: span.end() diff --git a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/package.py b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/package.py index d608d3476a..c79b14cfd7 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/package.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/package.py @@ -13,4 +13,4 @@ # limitations under the License. -_instruments = ("sqlalchemy",) +_instruments = ("sqlalchemy >= 1.3",) diff --git a/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py b/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py index 4a633687e6..84904ad859 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py @@ -11,13 +11,19 @@ # 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 Coroutine from unittest import mock from sqlalchemy import create_engine - +import sqlalchemy from opentelemetry import trace from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor from opentelemetry.test.test_base import TestBase +import asyncio + + +def _call_async(coro: Coroutine): + return asyncio.get_event_loop().run_until_complete(coro) class TestSqlalchemyInstrumentation(TestBase): @@ -28,7 +34,8 @@ def tearDown(self): def test_trace_integration(self): engine = create_engine("sqlite:///:memory:") SQLAlchemyInstrumentor().instrument( - engine=engine, tracer_provider=self.tracer_provider, + engine=engine, + tracer_provider=self.tracer_provider, ) cnx = engine.connect() cnx.execute("SELECT 1 + 1;").fetchall() @@ -38,6 +45,25 @@ def test_trace_integration(self): self.assertEqual(spans[0].name, "SELECT :memory:") self.assertEqual(spans[0].kind, trace.SpanKind.CLIENT) + def test_async_trace_integration(self): + if sqlalchemy.__version__.startswith("1.3"): + return + from sqlalchemy.ext.asyncio import ( + create_async_engine, + ) # pylint: disable-all + + engine = create_async_engine("sqlite+aiosqlite:///:memory:") + SQLAlchemyInstrumentor().instrument( + engine=engine.sync_engine, tracer_provider=self.tracer_provider + ) + cnx = _call_async(engine.connect()) + _call_async(cnx.execute(sqlalchemy.text("SELECT 1 + 1;"))).fetchall() + _call_async(cnx.close()) + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) + self.assertEqual(spans[0].name, "SELECT :memory:") + self.assertEqual(spans[0].kind, trace.SpanKind.CLIENT) + def test_not_recording(self): mock_tracer = mock.Mock() mock_span = mock.Mock() @@ -47,7 +73,8 @@ def test_not_recording(self): tracer.return_value = mock_tracer engine = create_engine("sqlite:///:memory:") SQLAlchemyInstrumentor().instrument( - engine=engine, tracer_provider=self.tracer_provider, + engine=engine, + tracer_provider=self.tracer_provider, ) cnx = engine.connect() cnx.execute("SELECT 1 + 1;").fetchall() 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 ef9cac051a..c48085a0e3 100644 --- a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_mssql.py +++ b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_mssql.py @@ -85,7 +85,8 @@ def test_engine_execute_errors(self): self.assertTrue(span.end_time - span.start_time > 0) # check the error self.assertIs( - span.status.status_code, trace.StatusCode.ERROR, + span.status.status_code, + trace.StatusCode.ERROR, ) self.assertIn("a_wrong_table", span.status.description) 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 c9e0a8dd1e..730939fd29 100644 --- a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_mysql.py +++ b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_mysql.py @@ -84,6 +84,7 @@ def test_engine_execute_errors(self): self.assertTrue(span.end_time - span.start_time > 0) # check the error self.assertIs( - span.status.status_code, trace.StatusCode.ERROR, + span.status.status_code, + trace.StatusCode.ERROR, ) self.assertIn("a_wrong_table", span.status.description) 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 d72791d970..62c880d140 100644 --- a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_postgres.py +++ b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_postgres.py @@ -78,7 +78,8 @@ def test_engine_execute_errors(self): self.assertTrue(span.end_time - span.start_time > 0) # check the error self.assertIs( - span.status.status_code, trace.StatusCode.ERROR, + span.status.status_code, + trace.StatusCode.ERROR, ) self.assertIn("a_wrong_table", span.status.description) 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 0acba0fec2..aa33b1ad0d 100644 --- a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_sqlite.py +++ b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_sqlite.py @@ -54,7 +54,8 @@ def test_engine_execute_errors(self): self.assertTrue((span.end_time - span.start_time) > 0) # check the error self.assertIs( - span.status.status_code, trace.StatusCode.ERROR, + span.status.status_code, + trace.StatusCode.ERROR, ) self.assertEqual( span.status.description, "no such table: a_wrong_table" diff --git a/tox.ini b/tox.ini index f720b8fd43..568346aff3 100644 --- a/tox.ini +++ b/tox.ini @@ -122,8 +122,8 @@ envlist = py3{6,7,8,9}-test-instrumentation-grpc ; opentelemetry-instrumentation-sqlalchemy - py3{6,7,8,9}-test-instrumentation-sqlalchemy - pypy3-test-instrumentation-sqlalchemy + py3{6,7,8,9}-test-instrumentation-sqlalchemy{13,14} + pypy3-test-instrumentation-sqlalchemy{13,14} ; opentelemetry-instrumentation-redis py3{6,7,8,9}-test-instrumentation-redis @@ -173,6 +173,9 @@ deps = elasticsearch6: elasticsearch>=6.0,<7.0 elasticsearch7: elasticsearch-dsl>=7.0,<8.0 elasticsearch7: elasticsearch>=7.0,<8.0 + sqlalchemy13: sqlalchemy~=1.3,<1.4 + sqlalchemy14: aiosqlite + sqlalchemy14: sqlalchemy~=1.4 ; FIXME: add coverage testing ; FIXME: add mypy testing @@ -290,7 +293,7 @@ commands_pre = sklearn: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-sklearn[test] - sqlalchemy: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-sqlalchemy[test] + sqlalchemy{13,14}: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-sqlalchemy[test] elasticsearch{2,5,6,7}: pip install {toxinidir}/opentelemetry-python-core/opentelemetry-instrumentation {toxinidir}/instrumentation/opentelemetry-instrumentation-elasticsearch[test] From ee58049ac5aeeaf911bbccd887b607df2e790265 Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Wed, 7 Jul 2021 10:37:53 +0100 Subject: [PATCH 07/19] lint fixes --- .../instrumentation/sqlalchemy/__init__.py | 10 +++++++ .../instrumentation/sqlalchemy/engine.py | 23 ++++++++++++---- .../tests/test_sqlalchemy.py | 26 ++++++++++++++++--- .../tests/sqlalchemy_tests/mixins.py | 4 ++- 4 files changed, 53 insertions(+), 10 deletions(-) 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 de85174e36..b5816a38d4 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/__init__.py @@ -60,6 +60,7 @@ from opentelemetry.instrumentation.sqlalchemy.engine import ( EngineTracer, _get_tracer, + _wrap_create_async_engine, _wrap_create_engine, ) from opentelemetry.instrumentation.sqlalchemy.package import _instruments @@ -88,6 +89,13 @@ def _instrument(self, **kwargs): """ _w("sqlalchemy", "create_engine", _wrap_create_engine) _w("sqlalchemy.engine", "create_engine", _wrap_create_engine) + if sqlalchemy.__version__.startswith("1.4"): + _w( + "sqlalchemy.ext.asyncio", + "create_async_engine", + _wrap_create_async_engine, + ) + if kwargs.get("engine") is not None: return EngineTracer( _get_tracer( @@ -100,3 +108,5 @@ def _instrument(self, **kwargs): def _uninstrument(self, **kwargs): unwrap(sqlalchemy, "create_engine") unwrap(sqlalchemy.engine, "create_engine") + if sqlalchemy.__version__.startswith("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 a924da7276..95b8230b31 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py @@ -42,6 +42,16 @@ def _get_tracer(engine, tracer_provider=None): ) +# pylint: disable=unused-argument +def _wrap_create_async_engine(func, module, args, kwargs): + """Trace the SQLAlchemy engine, creating an `EngineTracer` + object that will listen to SQLAlchemy events. + """ + engine = func(*args, **kwargs) + EngineTracer(_get_tracer(engine), engine.sync_engine) + return engine + + # pylint: disable=unused-argument def _wrap_create_engine(func, module, args, kwargs): """Trace the SQLAlchemy engine, creating an `EngineTracer` @@ -78,7 +88,9 @@ def _operation_name(self, db_name, statement): return " ".join(parts) # pylint: disable=unused-argument - def _before_cur_exec(self, conn, cursor, statement, params, context, executemany): + def _before_cur_exec( + self, conn, cursor, statement, params, context, executemany + ): attrs, found = _get_attributes_from_url(conn.engine.url) if not found: attrs = _get_attributes_from_cursor(self.vendor, cursor, attrs) @@ -98,15 +110,17 @@ def _before_cur_exec(self, conn, cursor, statement, params, context, executemany context._span = span # pylint: disable=unused-argument - def _after_cur_exec(self, conn, cursor, statement, params, context, executemany): - span = getattr(context, '_span', None) + def _after_cur_exec( + self, conn, cursor, statement, params, context, executemany + ): + span = getattr(context, "_span", None) if span is None: return span.end() def _handle_error(self, context): - span = getattr(context.execution_context, '_span', None) + span = getattr(context.execution_context, "_span", None) if span is None: return @@ -122,7 +136,6 @@ def _handle_error(self, context): span.end() - def _get_attributes_from_url(url): """Set connection tags from the url. return true if successful.""" attrs = {} diff --git a/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py b/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py index 84904ad859..caa7960211 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py @@ -11,15 +11,16 @@ # 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 Coroutine from unittest import mock -from sqlalchemy import create_engine import sqlalchemy +from sqlalchemy import create_engine + from opentelemetry import trace from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor from opentelemetry.test.test_base import TestBase -import asyncio def _call_async(coro: Coroutine): @@ -48,9 +49,9 @@ def test_trace_integration(self): def test_async_trace_integration(self): if sqlalchemy.__version__.startswith("1.3"): return - from sqlalchemy.ext.asyncio import ( + from sqlalchemy.ext.asyncio import ( # pylint: disable-all create_async_engine, - ) # pylint: disable-all + ) engine = create_async_engine("sqlite+aiosqlite:///:memory:") SQLAlchemyInstrumentor().instrument( @@ -95,3 +96,20 @@ def test_create_engine_wrapper(self): self.assertEqual(len(spans), 1) self.assertEqual(spans[0].name, "SELECT :memory:") self.assertEqual(spans[0].kind, trace.SpanKind.CLIENT) + + def test_create_async_engine_wrapper(self): + SQLAlchemyInstrumentor().instrument() + if sqlalchemy.__version__.startswith("1.3"): + return + from sqlalchemy.ext.asyncio import ( # pylint: disable-all + create_async_engine, + ) + + engine = create_async_engine("sqlite+aiosqlite:///:memory:") + cnx = _call_async(engine.connect()) + _call_async(cnx.execute(sqlalchemy.text("SELECT 1 + 1;"))).fetchall() + _call_async(cnx.close()) + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) + self.assertEqual(spans[0].name, "SELECT :memory:") + self.assertEqual(spans[0].kind, trace.SpanKind.CLIENT) diff --git a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py index c5fdd925ab..1493ce44cb 100644 --- a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py +++ b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py @@ -243,4 +243,6 @@ def insert_players(session): close_all_sessions() spans = self.memory_exporter.get_finished_spans() - self.assertEqual(len(spans), 5 if self.VENDOR not in ["postgresql"] else 3) + self.assertEqual( + len(spans), 5 if self.VENDOR not in ["postgresql"] else 3 + ) From c034e9f7e317d07714029a1afe1797b60a29353b Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Wed, 7 Jul 2021 11:53:36 +0100 Subject: [PATCH 08/19] run tox -e generate --- .../instrumentation/sqlalchemy/engine.py | 5 +- .../tests/test_sqlalchemy.py | 6 +- .../instrumentation/bootstrap_gen.py | 138 ++++++++++++++++++ 3 files changed, 141 insertions(+), 8 deletions(-) create mode 100644 opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py 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 95b8230b31..f04ce33449 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py @@ -127,10 +127,7 @@ def _handle_error(self, context): try: if span.is_recording(): span.set_status( - Status( - StatusCode.ERROR, - str(context.original_exception), - ) + Status(StatusCode.ERROR, str(context.original_exception),) ) finally: span.end() diff --git a/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py b/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py index caa7960211..361e48fd09 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py @@ -35,8 +35,7 @@ def tearDown(self): def test_trace_integration(self): engine = create_engine("sqlite:///:memory:") SQLAlchemyInstrumentor().instrument( - engine=engine, - tracer_provider=self.tracer_provider, + engine=engine, tracer_provider=self.tracer_provider, ) cnx = engine.connect() cnx.execute("SELECT 1 + 1;").fetchall() @@ -74,8 +73,7 @@ def test_not_recording(self): tracer.return_value = mock_tracer engine = create_engine("sqlite:///:memory:") SQLAlchemyInstrumentor().instrument( - engine=engine, - tracer_provider=self.tracer_provider, + engine=engine, tracer_provider=self.tracer_provider, ) cnx = engine.connect() cnx.execute("SELECT 1 + 1;").fetchall() diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py new file mode 100644 index 0000000000..ccbf116e35 --- /dev/null +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py @@ -0,0 +1,138 @@ +# 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 INSTRUMENTATION PACKAGES. +# RUN `python scripts/generate_instrumentation_bootstrap.py` TO REGENERATE. + +libraries = { + "aiohttp": { + "library": "aiohttp ~= 3.0", + "instrumentation": "opentelemetry-instrumentation-aiohttp-client==0.23.dev0", + }, + "aiopg": { + "library": "aiopg >= 0.13.0, < 1.3.0", + "instrumentation": "opentelemetry-instrumentation-aiopg==0.23.dev0", + }, + "asgiref": { + "library": "asgiref ~= 3.0", + "instrumentation": "opentelemetry-instrumentation-asgi==0.23.dev0", + }, + "asyncpg": { + "library": "asyncpg >= 0.12.0", + "instrumentation": "opentelemetry-instrumentation-asyncpg==0.23.dev0", + }, + "boto": { + "library": "boto~=2.0", + "instrumentation": "opentelemetry-instrumentation-boto==0.23.dev0", + }, + "botocore": { + "library": "botocore ~= 1.0", + "instrumentation": "opentelemetry-instrumentation-botocore==0.23.dev0", + }, + "celery": { + "library": "celery >= 4.0, < 6.0", + "instrumentation": "opentelemetry-instrumentation-celery==0.23.dev0", + }, + "django": { + "library": "django >= 1.10", + "instrumentation": "opentelemetry-instrumentation-django==0.23.dev0", + }, + "elasticsearch": { + "library": "elasticsearch >= 2.0", + "instrumentation": "opentelemetry-instrumentation-elasticsearch==0.23.dev0", + }, + "falcon": { + "library": "falcon ~= 2.0", + "instrumentation": "opentelemetry-instrumentation-falcon==0.23.dev0", + }, + "fastapi": { + "library": "fastapi ~= 0.58.1", + "instrumentation": "opentelemetry-instrumentation-fastapi==0.23.dev0", + }, + "flask": { + "library": "flask >= 1.0, < 3.0", + "instrumentation": "opentelemetry-instrumentation-flask==0.23.dev0", + }, + "grpcio": { + "library": "grpcio ~= 1.27", + "instrumentation": "opentelemetry-instrumentation-grpc==0.23.dev0", + }, + "httpx": { + "library": "httpx >= 0.18.0, < 0.19.0", + "instrumentation": "opentelemetry-instrumentation-httpx==0.23.dev0", + }, + "jinja2": { + "library": "jinja2~=2.7", + "instrumentation": "opentelemetry-instrumentation-jinja2==0.23.dev0", + }, + "mysql-connector-python": { + "library": "mysql-connector-python ~= 8.0", + "instrumentation": "opentelemetry-instrumentation-mysql==0.23.dev0", + }, + "psycopg2": { + "library": "psycopg2 >= 2.7.3.1", + "instrumentation": "opentelemetry-instrumentation-psycopg2==0.23.dev0", + }, + "pymemcache": { + "library": "pymemcache ~= 1.3", + "instrumentation": "opentelemetry-instrumentation-pymemcache==0.23.dev0", + }, + "pymongo": { + "library": "pymongo ~= 3.1", + "instrumentation": "opentelemetry-instrumentation-pymongo==0.23.dev0", + }, + "PyMySQL": { + "library": "PyMySQL ~= 0.10.1", + "instrumentation": "opentelemetry-instrumentation-pymysql==0.23.dev0", + }, + "pyramid": { + "library": "pyramid >= 1.7", + "instrumentation": "opentelemetry-instrumentation-pyramid==0.23.dev0", + }, + "redis": { + "library": "redis >= 2.6", + "instrumentation": "opentelemetry-instrumentation-redis==0.23.dev0", + }, + "requests": { + "library": "requests ~= 2.0", + "instrumentation": "opentelemetry-instrumentation-requests==0.23.dev0", + }, + "scikit-learn": { + "library": "scikit-learn ~= 0.24.0", + "instrumentation": "opentelemetry-instrumentation-sklearn==0.23.dev0", + }, + "sqlalchemy": { + "library": "sqlalchemy >= 1.3", + "instrumentation": "opentelemetry-instrumentation-sqlalchemy==0.23.dev0", + }, + "starlette": { + "library": "starlette ~= 0.13.0", + "instrumentation": "opentelemetry-instrumentation-starlette==0.23.dev0", + }, + "tornado": { + "library": "tornado >= 6.0", + "instrumentation": "opentelemetry-instrumentation-tornado==0.23.dev0", + }, + "urllib3": { + "library": "urllib3 >= 1.0.0, < 2.0.0", + "instrumentation": "opentelemetry-instrumentation-urllib3==0.23.dev0", + }, +} +default_instrumentations = [ + "opentelemetry-instrumentation-dbapi==0.23.dev0", + "opentelemetry-instrumentation-logging==0.23.dev0", + "opentelemetry-instrumentation-sqlite3==0.23.dev0", + "opentelemetry-instrumentation-urllib==0.23.dev0", + "opentelemetry-instrumentation-wsgi==0.23.dev0", +] From 0eabf8e7ec014266abd17c7e5d4002385c5a33c2 Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Wed, 7 Jul 2021 14:09:12 +0100 Subject: [PATCH 09/19] use pytest mark to skip tests. fix work directory for sqlalchemy tests --- .../tests/test_sqlalchemy.py | 37 +++++++++---------- tox.ini | 2 +- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py b/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py index 361e48fd09..84e38c5a54 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py @@ -11,10 +11,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -import asyncio -from typing import Coroutine -from unittest import mock +from unittest import IsolatedAsyncioTestCase, mock +import pytest import sqlalchemy from sqlalchemy import create_engine @@ -23,11 +22,7 @@ from opentelemetry.test.test_base import TestBase -def _call_async(coro: Coroutine): - return asyncio.get_event_loop().run_until_complete(coro) - - -class TestSqlalchemyInstrumentation(TestBase): +class TestSqlalchemyInstrumentation(TestBase, IsolatedAsyncioTestCase): def tearDown(self): super().tearDown() SQLAlchemyInstrumentor().uninstrument() @@ -45,9 +40,11 @@ def test_trace_integration(self): self.assertEqual(spans[0].name, "SELECT :memory:") self.assertEqual(spans[0].kind, trace.SpanKind.CLIENT) - def test_async_trace_integration(self): - if sqlalchemy.__version__.startswith("1.3"): - return + @pytest.mark.skipif( + sqlalchemy.__version__.startswith("1.3"), + reason="skipping async tests for 1.3", + ) + async def test_async_trace_integration(self): from sqlalchemy.ext.asyncio import ( # pylint: disable-all create_async_engine, ) @@ -56,9 +53,8 @@ def test_async_trace_integration(self): SQLAlchemyInstrumentor().instrument( engine=engine.sync_engine, tracer_provider=self.tracer_provider ) - cnx = _call_async(engine.connect()) - _call_async(cnx.execute(sqlalchemy.text("SELECT 1 + 1;"))).fetchall() - _call_async(cnx.close()) + 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:") @@ -95,18 +91,19 @@ def test_create_engine_wrapper(self): self.assertEqual(spans[0].name, "SELECT :memory:") self.assertEqual(spans[0].kind, trace.SpanKind.CLIENT) - def test_create_async_engine_wrapper(self): + @pytest.mark.skipif( + sqlalchemy.__version__.startswith("1.3"), + reason="skipping async tests for 1.3", + ) + async def test_create_async_engine_wrapper(self): SQLAlchemyInstrumentor().instrument() - if sqlalchemy.__version__.startswith("1.3"): - return from sqlalchemy.ext.asyncio import ( # pylint: disable-all create_async_engine, ) engine = create_async_engine("sqlite+aiosqlite:///:memory:") - cnx = _call_async(engine.connect()) - _call_async(cnx.execute(sqlalchemy.text("SELECT 1 + 1;"))).fetchall() - _call_async(cnx.close()) + 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:") diff --git a/tox.ini b/tox.ini index 568346aff3..5a9637f990 100644 --- a/tox.ini +++ b/tox.ini @@ -208,7 +208,7 @@ changedir = test-instrumentation-redis: instrumentation/opentelemetry-instrumentation-redis/tests test-instrumentation-requests: instrumentation/opentelemetry-instrumentation-requests/tests test-instrumentation-sklearn: instrumentation/opentelemetry-instrumentation-sklearn/tests - test-instrumentation-sqlalchemy: instrumentation/opentelemetry-instrumentation-sqlalchemy/tests + test-instrumentation-sqlalchemy{13,14}: instrumentation/opentelemetry-instrumentation-sqlalchemy/tests test-instrumentation-sqlite3: instrumentation/opentelemetry-instrumentation-sqlite3/tests test-instrumentation-starlette: instrumentation/opentelemetry-instrumentation-starlette/tests test-instrumentation-tornado: instrumentation/opentelemetry-instrumentation-tornado/tests From 83937c0315303666cbb0526cefc4a58de1ae165f Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Wed, 7 Jul 2021 14:11:13 +0100 Subject: [PATCH 10/19] fix lint issues --- .../tests/sqlalchemy_tests/test_mssql.py | 3 +-- .../tests/sqlalchemy_tests/test_mysql.py | 3 +-- .../tests/sqlalchemy_tests/test_postgres.py | 3 +-- .../tests/sqlalchemy_tests/test_sqlite.py | 3 +-- 4 files changed, 4 insertions(+), 8 deletions(-) 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 c48085a0e3..ef9cac051a 100644 --- a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_mssql.py +++ b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_mssql.py @@ -85,8 +85,7 @@ def test_engine_execute_errors(self): self.assertTrue(span.end_time - span.start_time > 0) # check the error self.assertIs( - span.status.status_code, - trace.StatusCode.ERROR, + span.status.status_code, trace.StatusCode.ERROR, ) self.assertIn("a_wrong_table", span.status.description) 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 730939fd29..c9e0a8dd1e 100644 --- a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_mysql.py +++ b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_mysql.py @@ -84,7 +84,6 @@ def test_engine_execute_errors(self): self.assertTrue(span.end_time - span.start_time > 0) # check the error self.assertIs( - span.status.status_code, - trace.StatusCode.ERROR, + span.status.status_code, trace.StatusCode.ERROR, ) self.assertIn("a_wrong_table", span.status.description) 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 62c880d140..d72791d970 100644 --- a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_postgres.py +++ b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_postgres.py @@ -78,8 +78,7 @@ def test_engine_execute_errors(self): self.assertTrue(span.end_time - span.start_time > 0) # check the error self.assertIs( - span.status.status_code, - trace.StatusCode.ERROR, + span.status.status_code, trace.StatusCode.ERROR, ) self.assertIn("a_wrong_table", span.status.description) 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 aa33b1ad0d..0acba0fec2 100644 --- a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_sqlite.py +++ b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/test_sqlite.py @@ -54,8 +54,7 @@ def test_engine_execute_errors(self): self.assertTrue((span.end_time - span.start_time) > 0) # check the error self.assertIs( - span.status.status_code, - trace.StatusCode.ERROR, + span.status.status_code, trace.StatusCode.ERROR, ) self.assertEqual( span.status.description, "no such table: a_wrong_table" From 9164d9dfa2916eddcd522ee083b2b7ea5ac34164 Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Wed, 7 Jul 2021 14:36:12 +0100 Subject: [PATCH 11/19] address pylint --- .../instrumentation/sqlalchemy/engine.py | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) 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 f04ce33449..94c49a5f84 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py @@ -69,8 +69,8 @@ def __init__(self, tracer, engine): self.vendor = _normalize_vendor(engine.name) listen(engine, "before_cursor_execute", self._before_cur_exec) - listen(engine, "after_cursor_execute", self._after_cur_exec) - listen(engine, "handle_error", self._handle_error) + listen(engine, "after_cursor_execute", _after_cur_exec) + listen(engine, "handle_error", _handle_error) def _operation_name(self, db_name, statement): parts = [] @@ -109,29 +109,29 @@ def _before_cur_exec( context._span = span - # pylint: disable=unused-argument - def _after_cur_exec( - self, conn, cursor, statement, params, context, executemany - ): - span = getattr(context, "_span", None) - if span is None: - return - +# pylint: disable=unused-argument +def _after_cur_exec( + conn, cursor, statement, params, context, executemany +): + span = getattr(context, "_span", None) + if span is None: + return + + span.end() + +def _handle_error(context): + span = getattr(context.execution_context, "_span", None) + if span is None: + return + + try: + if span.is_recording(): + span.set_status( + Status(StatusCode.ERROR, str(context.original_exception),) + ) + finally: span.end() - def _handle_error(self, context): - span = getattr(context.execution_context, "_span", None) - if span is None: - return - - try: - if span.is_recording(): - span.set_status( - Status(StatusCode.ERROR, str(context.original_exception),) - ) - finally: - span.end() - def _get_attributes_from_url(url): """Set connection tags from the url. return true if successful.""" From b51e4f1ccc9de446271f227492360bd3711aaa71 Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Wed, 7 Jul 2021 14:49:08 +0100 Subject: [PATCH 12/19] fix linter issues + generate --- .../src/opentelemetry/instrumentation/sqlalchemy/engine.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 94c49a5f84..dd132b516f 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py @@ -109,16 +109,16 @@ def _before_cur_exec( context._span = span + # pylint: disable=unused-argument -def _after_cur_exec( - conn, cursor, statement, params, context, executemany -): +def _after_cur_exec(conn, cursor, statement, params, context, executemany): span = getattr(context, "_span", None) if span is None: return span.end() + def _handle_error(context): span = getattr(context.execution_context, "_span", None) if span is None: From 07b2f2b075cf3a600776f7ec7c99299adb5e4fac Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Wed, 7 Jul 2021 15:10:45 +0100 Subject: [PATCH 13/19] remove IsolatedAsyncioTestCase as not supported in python < 3.8 --- .../tests/test_sqlalchemy.py | 63 ++++++++++--------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py b/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py index 84e38c5a54..7bacda34cf 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py @@ -11,7 +11,8 @@ # 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 IsolatedAsyncioTestCase, mock +import asyncio +from unittest import mock import pytest import sqlalchemy @@ -22,7 +23,7 @@ from opentelemetry.test.test_base import TestBase -class TestSqlalchemyInstrumentation(TestBase, IsolatedAsyncioTestCase): +class TestSqlalchemyInstrumentation(TestBase): def tearDown(self): super().tearDown() SQLAlchemyInstrumentor().uninstrument() @@ -44,21 +45,24 @@ def test_trace_integration(self): sqlalchemy.__version__.startswith("1.3"), reason="skipping async tests for 1.3", ) - async def test_async_trace_integration(self): - from sqlalchemy.ext.asyncio import ( # pylint: disable-all - create_async_engine, - ) + def test_async_trace_integration(self): + async def run(): + from sqlalchemy.ext.asyncio import ( # pylint: disable-all + create_async_engine, + ) - engine = create_async_engine("sqlite+aiosqlite:///:memory:") - SQLAlchemyInstrumentor().instrument( - engine=engine.sync_engine, tracer_provider=self.tracer_provider - ) - 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(spans[0].kind, trace.SpanKind.CLIENT) + engine = create_async_engine("sqlite+aiosqlite:///:memory:") + SQLAlchemyInstrumentor().instrument( + engine=engine.sync_engine, tracer_provider=self.tracer_provider + ) + 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(spans[0].kind, trace.SpanKind.CLIENT) + + asyncio.get_event_loop().run_until_complete(run()) def test_not_recording(self): mock_tracer = mock.Mock() @@ -95,16 +99,19 @@ def test_create_engine_wrapper(self): sqlalchemy.__version__.startswith("1.3"), reason="skipping async tests for 1.3", ) - async def test_create_async_engine_wrapper(self): - SQLAlchemyInstrumentor().instrument() - from sqlalchemy.ext.asyncio import ( # pylint: disable-all - create_async_engine, - ) + def test_create_async_engine_wrapper(self): + async def run(): + SQLAlchemyInstrumentor().instrument() + from sqlalchemy.ext.asyncio import ( # pylint: disable-all + create_async_engine, + ) - engine = create_async_engine("sqlite+aiosqlite:///:memory:") - 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(spans[0].kind, trace.SpanKind.CLIENT) + engine = create_async_engine("sqlite+aiosqlite:///:memory:") + 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(spans[0].kind, trace.SpanKind.CLIENT) + + asyncio.get_event_loop().run_until_complete(run()) From f1985f6a19c7d975f365c93243ec5e656c9f34ec Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Wed, 7 Jul 2021 17:29:51 +0100 Subject: [PATCH 14/19] update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fccf35d963..f522cd8ff2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#563](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/563)) - `opentelemetry-exporter-datadog` Datadog exporter should not use `unknown_service` as fallback resource service name. ([#570](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/570)) +- Add support for the async extension of SQLAlchemy (>= 1.4) + ([#568](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/568)) ### Added - `opentelemetry-instrumentation-httpx` Add `httpx` instrumentation From ae8ce9b46a65e21af016665c7ecb28ed95c6bbb3 Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Thu, 8 Jul 2021 11:08:18 +0100 Subject: [PATCH 15/19] address PR comments --- .../instrumentation/sqlalchemy/engine.py | 18 ++++++++---------- .../instrumentation/sqlalchemy/package.py | 2 +- .../tests/test_sqlalchemy.py | 8 ++++---- .../instrumentation/bootstrap_gen.py | 2 +- 4 files changed, 14 insertions(+), 16 deletions(-) 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 dd132b516f..ed1dfb1976 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/engine.py @@ -107,12 +107,12 @@ def _before_cur_exec( for key, value in attrs.items(): span.set_attribute(key, value) - context._span = span + context._otel_span = span # pylint: disable=unused-argument def _after_cur_exec(conn, cursor, statement, params, context, executemany): - span = getattr(context, "_span", None) + span = getattr(context, "_otel_span", None) if span is None: return @@ -120,17 +120,15 @@ def _after_cur_exec(conn, cursor, statement, params, context, executemany): def _handle_error(context): - span = getattr(context.execution_context, "_span", None) + span = getattr(context.execution_context, "_otel_span", None) if span is None: return - try: - if span.is_recording(): - span.set_status( - Status(StatusCode.ERROR, str(context.original_exception),) - ) - finally: - span.end() + if span.is_recording(): + span.set_status( + Status(StatusCode.ERROR, str(context.original_exception),) + ) + span.end() def _get_attributes_from_url(url): diff --git a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/package.py b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/package.py index c79b14cfd7..d608d3476a 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/package.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/package.py @@ -13,4 +13,4 @@ # limitations under the License. -_instruments = ("sqlalchemy >= 1.3",) +_instruments = ("sqlalchemy",) diff --git a/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py b/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py index 7bacda34cf..fd1d11e7d0 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py @@ -42,8 +42,8 @@ def test_trace_integration(self): self.assertEqual(spans[0].kind, trace.SpanKind.CLIENT) @pytest.mark.skipif( - sqlalchemy.__version__.startswith("1.3"), - reason="skipping async tests for 1.3", + not sqlalchemy.__version__.startswith("1.4"), + reason="on run async tests for 1.4", ) def test_async_trace_integration(self): async def run(): @@ -96,8 +96,8 @@ def test_create_engine_wrapper(self): self.assertEqual(spans[0].kind, trace.SpanKind.CLIENT) @pytest.mark.skipif( - sqlalchemy.__version__.startswith("1.3"), - reason="skipping async tests for 1.3", + not sqlalchemy.__version__.startswith("1.4"), + reason="on run async tests for 1.4", ) def test_create_async_engine_wrapper(self): async def run(): diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py index ccbf116e35..b49f40905f 100644 --- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py @@ -113,7 +113,7 @@ "instrumentation": "opentelemetry-instrumentation-sklearn==0.23.dev0", }, "sqlalchemy": { - "library": "sqlalchemy >= 1.3", + "library": "sqlalchemy", "instrumentation": "opentelemetry-instrumentation-sqlalchemy==0.23.dev0", }, "starlette": { From 3a7c84da2687ca9b2e8d484aba6f1fb28fba2a2b Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Sat, 10 Jul 2021 22:49:39 +0100 Subject: [PATCH 16/19] add comment about execute_values for psycopg2 dialect --- .../tests/sqlalchemy_tests/mixins.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py index 1493ce44cb..b9c766ad1c 100644 --- a/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py +++ b/tests/opentelemetry-docker-tests/tests/sqlalchemy_tests/mixins.py @@ -243,6 +243,10 @@ def insert_players(session): close_all_sessions() spans = self.memory_exporter.get_finished_spans() + + # SQLAlchemy 1.4 uses the `execute_values` extension of the psycopg2 dialect to + # 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 ) From 5e38f5a145296180cda28585f5e8b7f34e88f016 Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Wed, 14 Jul 2021 11:38:23 +0100 Subject: [PATCH 17/19] use SQLAlchemy 1.1 for tests --- .../tests/test_sqlalchemy.py | 4 ++-- tox.ini | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py b/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py index fd1d11e7d0..bed2b5f312 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/tests/test_sqlalchemy.py @@ -43,7 +43,7 @@ def test_trace_integration(self): @pytest.mark.skipif( not sqlalchemy.__version__.startswith("1.4"), - reason="on run async tests for 1.4", + reason="only run async tests for 1.4", ) def test_async_trace_integration(self): async def run(): @@ -97,7 +97,7 @@ def test_create_engine_wrapper(self): @pytest.mark.skipif( not sqlalchemy.__version__.startswith("1.4"), - reason="on run async tests for 1.4", + reason="only run async tests for 1.4", ) def test_create_async_engine_wrapper(self): async def run(): diff --git a/tox.ini b/tox.ini index 5a9637f990..e7a5445043 100644 --- a/tox.ini +++ b/tox.ini @@ -122,8 +122,8 @@ envlist = py3{6,7,8,9}-test-instrumentation-grpc ; opentelemetry-instrumentation-sqlalchemy - py3{6,7,8,9}-test-instrumentation-sqlalchemy{13,14} - pypy3-test-instrumentation-sqlalchemy{13,14} + py3{6,7,8,9}-test-instrumentation-sqlalchemy{11,14} + pypy3-test-instrumentation-sqlalchemy{11,14} ; opentelemetry-instrumentation-redis py3{6,7,8,9}-test-instrumentation-redis @@ -173,7 +173,7 @@ deps = elasticsearch6: elasticsearch>=6.0,<7.0 elasticsearch7: elasticsearch-dsl>=7.0,<8.0 elasticsearch7: elasticsearch>=7.0,<8.0 - sqlalchemy13: sqlalchemy~=1.3,<1.4 + sqlalchemy11: sqlalchemy>=1.1,<1.2 sqlalchemy14: aiosqlite sqlalchemy14: sqlalchemy~=1.4 @@ -208,7 +208,7 @@ changedir = test-instrumentation-redis: instrumentation/opentelemetry-instrumentation-redis/tests test-instrumentation-requests: instrumentation/opentelemetry-instrumentation-requests/tests test-instrumentation-sklearn: instrumentation/opentelemetry-instrumentation-sklearn/tests - test-instrumentation-sqlalchemy{13,14}: instrumentation/opentelemetry-instrumentation-sqlalchemy/tests + test-instrumentation-sqlalchemy{11,14}: instrumentation/opentelemetry-instrumentation-sqlalchemy/tests test-instrumentation-sqlite3: instrumentation/opentelemetry-instrumentation-sqlite3/tests test-instrumentation-starlette: instrumentation/opentelemetry-instrumentation-starlette/tests test-instrumentation-tornado: instrumentation/opentelemetry-instrumentation-tornado/tests @@ -293,7 +293,7 @@ commands_pre = sklearn: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-sklearn[test] - sqlalchemy{13,14}: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-sqlalchemy[test] + sqlalchemy{11,14}: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-sqlalchemy[test] elasticsearch{2,5,6,7}: pip install {toxinidir}/opentelemetry-python-core/opentelemetry-instrumentation {toxinidir}/instrumentation/opentelemetry-instrumentation-elasticsearch[test] From 83e0e4f71df40f73b3aa5313d5cac994d2912dac Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Sat, 24 Jul 2021 11:29:44 +0100 Subject: [PATCH 18/19] remove gen file --- .../instrumentation/bootstrap_gen.py | 138 ------------------ 1 file changed, 138 deletions(-) delete mode 100644 opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py deleted file mode 100644 index b49f40905f..0000000000 --- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/bootstrap_gen.py +++ /dev/null @@ -1,138 +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. - -# DO NOT EDIT. THIS FILE WAS AUTOGENERATED FROM INSTRUMENTATION PACKAGES. -# RUN `python scripts/generate_instrumentation_bootstrap.py` TO REGENERATE. - -libraries = { - "aiohttp": { - "library": "aiohttp ~= 3.0", - "instrumentation": "opentelemetry-instrumentation-aiohttp-client==0.23.dev0", - }, - "aiopg": { - "library": "aiopg >= 0.13.0, < 1.3.0", - "instrumentation": "opentelemetry-instrumentation-aiopg==0.23.dev0", - }, - "asgiref": { - "library": "asgiref ~= 3.0", - "instrumentation": "opentelemetry-instrumentation-asgi==0.23.dev0", - }, - "asyncpg": { - "library": "asyncpg >= 0.12.0", - "instrumentation": "opentelemetry-instrumentation-asyncpg==0.23.dev0", - }, - "boto": { - "library": "boto~=2.0", - "instrumentation": "opentelemetry-instrumentation-boto==0.23.dev0", - }, - "botocore": { - "library": "botocore ~= 1.0", - "instrumentation": "opentelemetry-instrumentation-botocore==0.23.dev0", - }, - "celery": { - "library": "celery >= 4.0, < 6.0", - "instrumentation": "opentelemetry-instrumentation-celery==0.23.dev0", - }, - "django": { - "library": "django >= 1.10", - "instrumentation": "opentelemetry-instrumentation-django==0.23.dev0", - }, - "elasticsearch": { - "library": "elasticsearch >= 2.0", - "instrumentation": "opentelemetry-instrumentation-elasticsearch==0.23.dev0", - }, - "falcon": { - "library": "falcon ~= 2.0", - "instrumentation": "opentelemetry-instrumentation-falcon==0.23.dev0", - }, - "fastapi": { - "library": "fastapi ~= 0.58.1", - "instrumentation": "opentelemetry-instrumentation-fastapi==0.23.dev0", - }, - "flask": { - "library": "flask >= 1.0, < 3.0", - "instrumentation": "opentelemetry-instrumentation-flask==0.23.dev0", - }, - "grpcio": { - "library": "grpcio ~= 1.27", - "instrumentation": "opentelemetry-instrumentation-grpc==0.23.dev0", - }, - "httpx": { - "library": "httpx >= 0.18.0, < 0.19.0", - "instrumentation": "opentelemetry-instrumentation-httpx==0.23.dev0", - }, - "jinja2": { - "library": "jinja2~=2.7", - "instrumentation": "opentelemetry-instrumentation-jinja2==0.23.dev0", - }, - "mysql-connector-python": { - "library": "mysql-connector-python ~= 8.0", - "instrumentation": "opentelemetry-instrumentation-mysql==0.23.dev0", - }, - "psycopg2": { - "library": "psycopg2 >= 2.7.3.1", - "instrumentation": "opentelemetry-instrumentation-psycopg2==0.23.dev0", - }, - "pymemcache": { - "library": "pymemcache ~= 1.3", - "instrumentation": "opentelemetry-instrumentation-pymemcache==0.23.dev0", - }, - "pymongo": { - "library": "pymongo ~= 3.1", - "instrumentation": "opentelemetry-instrumentation-pymongo==0.23.dev0", - }, - "PyMySQL": { - "library": "PyMySQL ~= 0.10.1", - "instrumentation": "opentelemetry-instrumentation-pymysql==0.23.dev0", - }, - "pyramid": { - "library": "pyramid >= 1.7", - "instrumentation": "opentelemetry-instrumentation-pyramid==0.23.dev0", - }, - "redis": { - "library": "redis >= 2.6", - "instrumentation": "opentelemetry-instrumentation-redis==0.23.dev0", - }, - "requests": { - "library": "requests ~= 2.0", - "instrumentation": "opentelemetry-instrumentation-requests==0.23.dev0", - }, - "scikit-learn": { - "library": "scikit-learn ~= 0.24.0", - "instrumentation": "opentelemetry-instrumentation-sklearn==0.23.dev0", - }, - "sqlalchemy": { - "library": "sqlalchemy", - "instrumentation": "opentelemetry-instrumentation-sqlalchemy==0.23.dev0", - }, - "starlette": { - "library": "starlette ~= 0.13.0", - "instrumentation": "opentelemetry-instrumentation-starlette==0.23.dev0", - }, - "tornado": { - "library": "tornado >= 6.0", - "instrumentation": "opentelemetry-instrumentation-tornado==0.23.dev0", - }, - "urllib3": { - "library": "urllib3 >= 1.0.0, < 2.0.0", - "instrumentation": "opentelemetry-instrumentation-urllib3==0.23.dev0", - }, -} -default_instrumentations = [ - "opentelemetry-instrumentation-dbapi==0.23.dev0", - "opentelemetry-instrumentation-logging==0.23.dev0", - "opentelemetry-instrumentation-sqlite3==0.23.dev0", - "opentelemetry-instrumentation-urllib==0.23.dev0", - "opentelemetry-instrumentation-wsgi==0.23.dev0", -] From 125de32a2c9e5a6d59e47b48f043bba8d0ffcc5c Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Sat, 24 Jul 2021 11:43:05 +0100 Subject: [PATCH 19/19] use packaging to test versions --- .../src/opentelemetry/instrumentation/sqlalchemy/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 b5816a38d4..05e6451626 100644 --- a/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-sqlalchemy/src/opentelemetry/instrumentation/sqlalchemy/__init__.py @@ -54,6 +54,7 @@ from typing import Collection import sqlalchemy +from packaging.version import parse as parse_version from wrapt import wrap_function_wrapper as _w from opentelemetry.instrumentation.instrumentor import BaseInstrumentor @@ -89,7 +90,7 @@ def _instrument(self, **kwargs): """ _w("sqlalchemy", "create_engine", _wrap_create_engine) _w("sqlalchemy.engine", "create_engine", _wrap_create_engine) - if sqlalchemy.__version__.startswith("1.4"): + if parse_version(sqlalchemy.__version__).release >= (1, 4): _w( "sqlalchemy.ext.asyncio", "create_async_engine", @@ -108,5 +109,5 @@ def _instrument(self, **kwargs): def _uninstrument(self, **kwargs): unwrap(sqlalchemy, "create_engine") unwrap(sqlalchemy.engine, "create_engine") - if sqlalchemy.__version__.startswith("1.4"): + if parse_version(sqlalchemy.__version__).release >= (1, 4): unwrap(sqlalchemy.ext.asyncio, "create_async_engine")