Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d2da8cd
enable_commenter for instrument_connection psycopg(2)
tammy-baylis-swi Dec 5, 2024
4783f3a
Changelog
tammy-baylis-swi Dec 5, 2024
bf49c8c
Add psycopg2 tests
tammy-baylis-swi Dec 7, 2024
e148c9f
Add psycopg tests
tammy-baylis-swi Dec 7, 2024
58daeb6
Update typehints
tammy-baylis-swi Dec 7, 2024
8e8b759
Document psycopg(2) instrument_connection
tammy-baylis-swi Dec 7, 2024
e3d7941
Consistent pg_connection alias usage
tammy-baylis-swi Dec 11, 2024
a94a591
Revert "Consistent pg_connection alias usage"
tammy-baylis-swi Dec 11, 2024
0e24d6d
Use qualified import not alias
tammy-baylis-swi Dec 11, 2024
da529bd
Fix psycopg2/3 docs
tammy-baylis-swi Dec 11, 2024
197e5ea
Merge branch 'main' into enable-commenter-instrument-connection
tammy-baylis-swi Dec 11, 2024
b3ae9c8
Merge branch 'main' into enable-commenter-instrument-connection
tammy-baylis-swi Dec 11, 2024
64941f9
Merge branch 'main' into enable-commenter-instrument-connection
tammy-baylis-swi Dec 19, 2024
e6ca8fe
Merge branch 'main' into enable-commenter-instrument-connection
tammy-baylis-swi Jan 2, 2025
84d82d0
Fix
tammy-baylis-swi Jan 2, 2025
65ac19a
Merge branch 'main' into enable-commenter-instrument-connection
tammy-baylis-swi Jan 7, 2025
1ca0cd0
Merge branch 'main' into enable-commenter-instrument-connection
tammy-baylis-swi Jan 8, 2025
afff465
Merge branch 'main' into enable-commenter-instrument-connection
tammy-baylis-swi Jan 10, 2025
fd11f1a
Merge branch 'main' into enable-commenter-instrument-connection
tammy-baylis-swi Jan 13, 2025
90051b7
Merge branch 'main' into enable-commenter-instrument-connection
tammy-baylis-swi Jan 14, 2025
3587846
psycopg(2) instrument_connection support enable_attr_commenter
tammy-baylis-swi Jan 14, 2025
a738d5e
Merge branch 'main' into enable-commenter-instrument-connection
tammy-baylis-swi Jan 14, 2025
eeab531
Merge branch 'main' into enable-commenter-instrument-connection
tammy-baylis-swi Jan 15, 2025
e3db4a8
Fix imports after main changes
tammy-baylis-swi Jan 15, 2025
1526aaa
Merge branch 'main' into enable-commenter-instrument-connection
tammy-baylis-swi Jan 22, 2025
e081502
Merge branch 'main' into enable-commenter-instrument-connection
tammy-baylis-swi Feb 27, 2025
48c8a9f
Changelog
tammy-baylis-swi Feb 27, 2025
5e81f06
Merge branch 'main' into enable-commenter-instrument-connection
tammy-baylis-swi May 1, 2025
cdfc2e5
Merge branch 'main' into enable-commenter-instrument-connection
tammy-baylis-swi Feb 19, 2026
458d297
Add to async_factory
tammy-baylis-swi Feb 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([#4141](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4141))
- `opentelemetry-instrumentation-pyramid`: pass request attributes at span creation
([#4139](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4139))
- `opentelemetry-instrumentation-psycopg2`, `opentelemetry-instrumentation-psycopg`: Add sqlcommenter support for `instrument_connection`
([#3071](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3071))

### Fixed

Expand Down
1 change: 1 addition & 0 deletions docs-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ mysqlclient~=2.1.1
openai >= 1.26.0
psutil>=5
psycopg~=3.1.17
psycopg2~=2.9.9
pika>=0.12.0
pymongo~=4.6.3
PyMySQL~=1.1.1
Expand Down
2 changes: 2 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@
"https://opentelemetry-python.readthedocs.io/en/latest/",
None,
),
"psycopg": ("https://www.psycopg.org/psycopg3/docs/", None),
"psycopg2": ("https://www.psycopg.org/docs/", None),
"redis": ("https://redis.readthedocs.io/en/latest/", None),
"fsspec": ("https://filesystem-spec.readthedocs.io/en/latest/", None),
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@
from opentelemetry.instrumentation.psycopg import PsycopgInstrumentor

PsycopgInstrumentor().instrument(enable_commenter=True)
# OR with specific connection
cnx = psycopg.connect(database='Database')
instrumented_cnx = PsycopgInstrumentor().instrument_connection(
cnx,
enable_commenter=True,
)



SQLCommenter with commenter_options
Expand Down Expand Up @@ -144,11 +151,12 @@
from __future__ import annotations

import logging
from typing import Any, Callable, Collection, TypeVar
from typing import Any, Callable, Collection, Optional, TypeVar

import psycopg # pylint: disable=import-self
from psycopg.sql import Composable # pylint: disable=no-name-in-module

from opentelemetry import trace as trace_api
from opentelemetry.instrumentation import dbapi
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
from opentelemetry.instrumentation.psycopg.package import _instruments
Expand Down Expand Up @@ -179,7 +187,7 @@ def instrumentation_dependencies(self) -> Collection[str]:

def _instrument(self, **kwargs: Any):
"""Integrate with PostgreSQL Psycopg library.
Psycopg: http://initd.org/psycopg/
Psycopg: https://www.psycopg.org/psycopg3/docs/
"""
tracer_provider = kwargs.get("tracer_provider")
enable_sqlcommenter = kwargs.get("enable_commenter", False)
Expand Down Expand Up @@ -247,16 +255,26 @@ def _uninstrument(self, **kwargs: Any):
# TODO(owais): check if core dbapi can do this for all dbapi implementations e.g, pymysql and mysql
@staticmethod
def instrument_connection(
connection: ConnectionT, tracer_provider: TracerProvider | None = None
) -> ConnectionT:
"""Enable instrumentation in a psycopg connection.
connection: psycopg.Connection,
tracer_provider: Optional[trace_api.TracerProvider] = None,
enable_commenter: bool = False,
commenter_options: dict = None,
Comment thread
tammy-baylis-swi marked this conversation as resolved.
enable_attribute_commenter: bool = False,
):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we keep the old return type of -> ConnectionT?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! I've done this in other PR (#4267)

"""Enable instrumentation of a Psycopg connection.

Args:
connection: psycopg.Connection
The psycopg connection object to be instrumented.
tracer_provider: opentelemetry.trace.TracerProvider, optional
The TracerProvider to use for instrumentation. If not provided,
the global TracerProvider will be used.
enable_commenter: bool, optional
Optional flag to enable/disable sqlcommenter (default False).
commenter_options: dict, optional
Optional configurations for tags to be appended at the sql query.
enable_attribute_commenter:
Optional flag to enable/disable addition of sqlcomment to span attribute (default False). Requires enable_commenter=True.

Returns:
An instrumented psycopg connection object.
Expand All @@ -270,11 +288,17 @@ def instrument_connection(
)
if isinstance(connection, psycopg.AsyncConnection):
connection.cursor_factory = _new_cursor_async_factory(
tracer_provider=tracer_provider
tracer_provider=tracer_provider,
enable_commenter=enable_commenter,
commenter_options=commenter_options,
enable_attribute_commenter=enable_attribute_commenter,
)
else:
connection.cursor_factory = _new_cursor_factory(
tracer_provider=tracer_provider
tracer_provider=tracer_provider,
enable_commenter=enable_commenter,
commenter_options=commenter_options,
enable_attribute_commenter=enable_attribute_commenter,
)
connection._is_instrumented_by_opentelemetry = True
else:
Expand Down Expand Up @@ -359,9 +383,12 @@ def get_statement(self, cursor: CursorT, args: list[Any]) -> str:


def _new_cursor_factory(
db_api: DatabaseApiIntegration | None = None,
base_factory: type[psycopg.Cursor] | None = None,
tracer_provider: TracerProvider | None = None,
db_api: dbapi.DatabaseApiIntegration = None,
base_factory: psycopg.Cursor = None,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should keep as base_factory: type[psycopg.Cursor] | None = None

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! Kept in other PR (#4267)

tracer_provider: Optional[trace_api.TracerProvider] = None,
enable_commenter: bool = False,
commenter_options: dict = None,
enable_attribute_commenter: bool = False,
):
if not db_api:
db_api = DatabaseApiIntegration(
Expand All @@ -370,6 +397,10 @@ def _new_cursor_factory(
connection_attributes=PsycopgInstrumentor._CONNECTION_ATTRIBUTES,
version=__version__,
tracer_provider=tracer_provider,
enable_commenter=enable_commenter,
commenter_options=commenter_options,
connect_module=psycopg,
enable_attribute_commenter=enable_attribute_commenter,
)

base_factory = base_factory or psycopg.Cursor
Expand Down Expand Up @@ -398,6 +429,9 @@ def _new_cursor_async_factory(
db_api: DatabaseApiAsyncIntegration | None = None,
base_factory: type[psycopg.AsyncCursor] | None = None,
tracer_provider: TracerProvider | None = None,
enable_commenter: bool = False,
commenter_options: dict = None,
enable_attribute_commenter: bool = False,
):
if not db_api:
db_api = DatabaseApiAsyncIntegration(
Expand All @@ -406,6 +440,10 @@ def _new_cursor_async_factory(
connection_attributes=PsycopgInstrumentor._CONNECTION_ATTRIBUTES,
version=__version__,
tracer_provider=tracer_provider,
enable_commenter=enable_commenter,
commenter_options=commenter_options,
connect_module=psycopg,
enable_attribute_commenter=enable_attribute_commenter,
)
base_factory = base_factory or psycopg.AsyncCursor
_cursor_tracer = CursorTracer(db_api)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import opentelemetry.instrumentation.psycopg
from opentelemetry.instrumentation.psycopg import PsycopgInstrumentor
from opentelemetry.sdk import resources
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.test.test_base import TestBase


Expand Down Expand Up @@ -451,6 +452,149 @@ def test_sqlcommenter_enabled(self, event_mocked):
kwargs = event_mocked.call_args[1]
self.assertEqual(kwargs["enable_commenter"], True)

def test_sqlcommenter_enabled_instrument_connection_defaults(self):
with (
mock.patch(
"opentelemetry.instrumentation.psycopg.psycopg.__version__",
"foobar",
),
mock.patch(
"opentelemetry.instrumentation.psycopg.psycopg.pq.__build_version__",
"foobaz",
),
mock.patch(
"opentelemetry.instrumentation.psycopg.psycopg.pq.version",
new=lambda: "foobaz",
),
mock.patch(
"opentelemetry.instrumentation.psycopg.psycopg.threadsafety",
"123",
),
mock.patch(
"opentelemetry.instrumentation.psycopg.psycopg.apilevel",
"123",
),
mock.patch(
"opentelemetry.instrumentation.psycopg.psycopg.paramstyle",
"test",
),
):
cnx = psycopg.connect(database="test")
cnx = PsycopgInstrumentor().instrument_connection(
cnx,
enable_commenter=True,
)
query = "Select 1"
cursor = cnx.cursor()
cursor.execute(query)
spans_list = self.memory_exporter.get_finished_spans()
span = spans_list[0]
span_id = format(span.get_span_context().span_id, "016x")
trace_id = format(span.get_span_context().trace_id, "032x")
self.assertEqual(
MockCursor.execute.call_args[0][0],
f"Select 1 /*db_driver='psycopg%%3Afoobar',dbapi_level='123',dbapi_threadsafety='123',driver_paramstyle='test',libpq_version='foobaz',traceparent='00-{trace_id}-{span_id}-01'*/",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we update this to use {trace_flags:02x} instead of hard coding 01 for the trace flags since this will change with the introduction of the random trace id flag. Context: open-telemetry/opentelemetry-python#4854

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great call. I've done this in the other PR (#4267)

)
self.assertEqual(
span.attributes[SpanAttributes.DB_STATEMENT],
"Select 1",
)

def test_sqlcommenter_enabled_instrument_connection_stmt_enabled(self):
with (
mock.patch(
"opentelemetry.instrumentation.psycopg.psycopg.__version__",
"foobar",
),
mock.patch(
"opentelemetry.instrumentation.psycopg.psycopg.pq.__build_version__",
"foobaz",
),
mock.patch(
"opentelemetry.instrumentation.psycopg.psycopg.pq.version",
new=lambda: "foobaz",
),
mock.patch(
"opentelemetry.instrumentation.psycopg.psycopg.threadsafety",
"123",
),
mock.patch(
"opentelemetry.instrumentation.psycopg.psycopg.apilevel",
"123",
),
mock.patch(
"opentelemetry.instrumentation.psycopg.psycopg.paramstyle",
"test",
),
):
cnx = psycopg.connect(database="test")
cnx = PsycopgInstrumentor().instrument_connection(
cnx,
enable_commenter=True,
enable_attribute_commenter=True,
)
query = "Select 1"
cursor = cnx.cursor()
cursor.execute(query)
spans_list = self.memory_exporter.get_finished_spans()
span = spans_list[0]
span_id = format(span.get_span_context().span_id, "016x")
trace_id = format(span.get_span_context().trace_id, "032x")
self.assertEqual(
MockCursor.execute.call_args[0][0],
f"Select 1 /*db_driver='psycopg%%3Afoobar',dbapi_level='123',dbapi_threadsafety='123',driver_paramstyle='test',libpq_version='foobaz',traceparent='00-{trace_id}-{span_id}-01'*/",
)
self.assertEqual(
span.attributes[SpanAttributes.DB_STATEMENT],
f"Select 1 /*db_driver='psycopg%%3Afoobar',dbapi_level='123',dbapi_threadsafety='123',driver_paramstyle='test',libpq_version='foobaz',traceparent='00-{trace_id}-{span_id}-01'*/",
)

def test_sqlcommenter_enabled_instrument_connection_with_options(self):
with (
mock.patch(
"opentelemetry.instrumentation.psycopg.psycopg.__version__",
"foobar",
),
mock.patch(
"opentelemetry.instrumentation.psycopg.psycopg.pq.__build_version__",
"foobaz",
),
mock.patch(
"opentelemetry.instrumentation.psycopg.psycopg.pq.version",
new=lambda: "foobaz",
),
mock.patch(
"opentelemetry.instrumentation.psycopg.psycopg.threadsafety",
"123",
),
):
cnx = psycopg.connect(database="test")
cnx = PsycopgInstrumentor().instrument_connection(
cnx,
enable_commenter=True,
commenter_options={
"dbapi_level": False,
"dbapi_threadsafety": True,
"driver_paramstyle": False,
"foo": "ignored",
},
)
query = "Select 1"
cursor = cnx.cursor()
cursor.execute(query)
spans_list = self.memory_exporter.get_finished_spans()
span = spans_list[0]
span_id = format(span.get_span_context().span_id, "016x")
trace_id = format(span.get_span_context().trace_id, "032x")
self.assertEqual(
MockCursor.execute.call_args[0][0],
f"Select 1 /*db_driver='psycopg%%3Afoobar',dbapi_threadsafety='123',libpq_version='foobaz',traceparent='00-{trace_id}-{span_id}-01'*/",
)
self.assertEqual(
span.attributes[SpanAttributes.DB_STATEMENT],
"Select 1",
)

@mock.patch("opentelemetry.instrumentation.dbapi.wrap_connect")
def test_sqlcommenter_disabled(self, event_mocked):
cnx = psycopg.connect(database="test")
Expand All @@ -461,6 +605,45 @@ def test_sqlcommenter_disabled(self, event_mocked):
kwargs = event_mocked.call_args[1]
self.assertEqual(kwargs["enable_commenter"], False)

def test_sqlcommenter_disabled_default_instrument_connection(self):
cnx = psycopg.connect(database="test")
cnx = PsycopgInstrumentor().instrument_connection(
cnx,
)
query = "Select 1"
cursor = cnx.cursor()
cursor.execute(query)
self.assertEqual(
MockCursor.execute.call_args[0][0],
"Select 1",
)
spans_list = self.memory_exporter.get_finished_spans()
span = spans_list[0]
self.assertEqual(
span.attributes[SpanAttributes.DB_STATEMENT],
"Select 1",
)

def test_sqlcommenter_disabled_explicit_instrument_connection(self):
cnx = psycopg.connect(database="test")
cnx = PsycopgInstrumentor().instrument_connection(
cnx,
enable_commenter=False,
)
query = "Select 1"
cursor = cnx.cursor()
cursor.execute(query)
self.assertEqual(
MockCursor.execute.call_args[0][0],
"Select 1",
)
spans_list = self.memory_exporter.get_finished_spans()
span = spans_list[0]
self.assertEqual(
span.attributes[SpanAttributes.DB_STATEMENT],
"Select 1",
)


class TestPostgresqlIntegrationAsync(
PostgresqlIntegrationTestMixin, TestBase, IsolatedAsyncioTestCase
Expand Down
Loading