diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f649e388..13f058460 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -104,6 +104,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#3922](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3922)) - `opentelemetry-instrumentation-urllib3`: fix multiple arguments error ([#4144](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4144)) +- `opentelemetry-instrumentation-psycopg`: Fix instrument of typed psycopg sql + ([#4078](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4171)) - `opentelemetry-instrumentation-aiohttp-server`: fix HTTP error inconsistencies ([#4175](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4175)) @@ -112,7 +114,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `opentelemetry-instrumentation-logging`: Inject span context attributes into logging LogRecord only if configured to do so ([#4112](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4112)) - `opentelemetry-instrumentation-django`: Drop support for Django < 2.0 - ([#3848](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4083)) + ([#4083](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4083)) ## Version 1.39.0/0.60b0 (2025-12-03) 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 362252ee0..28896be13 100644 --- a/instrumentation/opentelemetry-instrumentation-psycopg/src/opentelemetry/instrumentation/psycopg/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-psycopg/src/opentelemetry/instrumentation/psycopg/__init__.py @@ -147,7 +147,7 @@ import logging from typing import Any, Callable, Collection, TypeVar import psycopg # pylint: disable=import-self -from psycopg.sql import Composed # pylint: disable=no-name-in-module +from psycopg.sql import Composable # pylint: disable=no-name-in-module from opentelemetry.instrumentation import dbapi from opentelemetry.instrumentation.instrumentor import BaseInstrumentor @@ -338,7 +338,7 @@ class CursorTracer(dbapi.CursorTracer): return "" statement = args[0] - if isinstance(statement, Composed): + if isinstance(statement, Composable): statement = statement.as_string(cursor) # `statement` can be empty string. See #2643 @@ -353,7 +353,7 @@ class CursorTracer(dbapi.CursorTracer): return "" statement = args[0] - if isinstance(statement, Composed): + if isinstance(statement, Composable): statement = statement.as_string(cursor) return statement diff --git a/instrumentation/opentelemetry-instrumentation-psycopg/tests/test_psycopg_integration.py b/instrumentation/opentelemetry-instrumentation-psycopg/tests/test_psycopg_integration.py index 531d5b021..84d6709bb 100644 --- a/instrumentation/opentelemetry-instrumentation-psycopg/tests/test_psycopg_integration.py +++ b/instrumentation/opentelemetry-instrumentation-psycopg/tests/test_psycopg_integration.py @@ -17,6 +17,7 @@ import types from unittest import IsolatedAsyncioTestCase, mock import psycopg +from psycopg.sql import SQL, Composed import opentelemetry.instrumentation.psycopg from opentelemetry.instrumentation.psycopg import PsycopgInstrumentor @@ -34,6 +35,8 @@ class MockCursor: callproc = mock.MagicMock(spec=types.MethodType) callproc.__name__ = "callproc" + connection = None + rowcount = "SomeRowCount" def __init__(self, *args, **kwargs): @@ -348,6 +351,41 @@ class TestPostgresqlIntegration(PostgresqlIntegrationTestMixin, TestBase): spans_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(spans_list), 1) + def test_instrument_connection_typed_sql_query(self): + cnx = psycopg.connect(database="test") + query = SQL("SELECT * FROM test") + + cnx = PsycopgInstrumentor().instrument_connection(cnx) + + self.assertTrue(issubclass(cnx.cursor_factory, MockCursor)) + + cursor = cnx.cursor() + cursor.execute(query) + + spans_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans_list), 1) + self.assertEqual(spans_list[0].name, "SELECT") + self.assertEqual( + spans_list[0].attributes["db.statement"], "SELECT * FROM test" + ) + + def test_instrument_connection_composed_query(self): + cnx = psycopg.connect(database="test") + query: Composed = SQL("SELECT * FROM test").format() + + cnx = PsycopgInstrumentor().instrument_connection(cnx) + self.assertTrue(issubclass(cnx.cursor_factory, MockCursor)) + + cursor = cnx.cursor() + cursor.execute(query) + + spans_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans_list), 1) + self.assertEqual(spans_list[0].name, "SELECT") + self.assertEqual( + spans_list[0].attributes["db.statement"], "SELECT * FROM test" + ) + # pylint: disable=unused-argument def test_instrument_connection_with_instrument(self): cnx = psycopg.connect(database="test")